funboost 48.8__py3-none-any.whl → 49.0__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.

Potentially problematic release.


This version of funboost might be problematic. Click here for more details.

Files changed (33) hide show
  1. funboost/__init__.py +1 -1
  2. funboost/concurrent_pool/custom_threadpool_executor.py +1 -1
  3. funboost/constant.py +16 -2
  4. funboost/consumers/base_consumer.py +42 -29
  5. funboost/consumers/rabbitmq_amqpstorm_consumer.py +5 -0
  6. funboost/consumers/redis_filter.py +47 -31
  7. funboost/core/active_cousumer_info_getter.py +47 -7
  8. funboost/core/booster.py +1 -0
  9. funboost/core/current_task.py +17 -0
  10. funboost/core/func_params_model.py +30 -17
  11. funboost/core/loggers.py +1 -0
  12. funboost/funboost_config_deafult.py +1 -1
  13. funboost/function_result_web/__pycache__/app.cpython-37.pyc +0 -0
  14. funboost/function_result_web/__pycache__/functions.cpython-37.pyc +0 -0
  15. funboost/function_result_web/__pycache__/functions.cpython-39.pyc +0 -0
  16. funboost/function_result_web/app_debug_start.py +1 -1
  17. funboost/function_result_web/functions.py +10 -1
  18. funboost/function_result_web/static/js/form-memory.js +92 -0
  19. funboost/function_result_web/static/js_cdn/chart.js +20 -0
  20. funboost/function_result_web/templates/index.html +31 -1
  21. funboost/function_result_web/templates/queue_op.html +418 -27
  22. funboost/function_result_web/templates/rpc_call.html +51 -37
  23. funboost/publishers/rabbitmq_amqpstorm_publisher.py +1 -1
  24. funboost/publishers/redis_publisher_priority.py +2 -2
  25. funboost/utils/dependency_packages_in_pythonpath/aioredis/readme.md +1 -1
  26. funboost/utils/dependency_packages_in_pythonpath/readme.md +1 -1
  27. {funboost-48.8.dist-info → funboost-49.0.dist-info}/METADATA +176 -82
  28. {funboost-48.8.dist-info → funboost-49.0.dist-info}/RECORD +32 -31
  29. {funboost-48.8.dist-info → funboost-49.0.dist-info}/WHEEL +1 -1
  30. funboost/function_result_web/templates/index_/321/204/342/225/225/320/235/321/205/320/237/320/277/321/206/320/232/320/250/321/205/320/237/320/260.html +0 -153
  31. {funboost-48.8.dist-info → funboost-49.0.dist-info}/LICENSE +0 -0
  32. {funboost-48.8.dist-info → funboost-49.0.dist-info}/entry_points.txt +0 -0
  33. {funboost-48.8.dist-info → funboost-49.0.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,3 @@
1
-
2
-
3
-
4
1
  <!DOCTYPE html>
5
2
  <html lang="zh">
6
3
  <head>
@@ -23,6 +20,7 @@
23
20
  <!-- Tabulator JS -->
24
21
  <script type="text/javascript" src="{{ url_for('static',filename='js_cdn/tabulator-tables@5.5.0/dist/js/tabulator.min.js') }}"></script>
25
22
  <!-- <script src="https://cdn.bootcdn.net/ajax/libs/tabulator/5.5.0/js/tabulator.min.js"></script> -->
23
+ <script src="{{ url_for('static',filename='js_cdn/chart.js') }}"></script>
26
24
 
27
25
  <style>
28
26
  .action-btn {
@@ -35,35 +33,105 @@
35
33
  width: 500px;
36
34
  display: inline-block;
37
35
  }
36
+ /* .frozen-column-background { background-color: #FFFFFF !important; } */ /* 移除或注释掉这里 */
37
+ .tabulator-cell {
38
+ padding-left: 20px !important;
39
+ padding-right: 20px !important;
40
+ padding-top: 10px !important;
41
+ border: 1px solid #555 !important;
42
+ background-color: #000000; /* 移除 !important */
43
+ color: #FFFFFF; /* 移除 !important */
44
+ }
45
+ /* 新增: 自定义超大模态框样式 */
46
+ .modal-xl-custom {
47
+ width: 80%; /* 宽度占屏幕的80% */
48
+ max-width: 1400px; /* 最大宽度限制 */
49
+ }
38
50
  </style>
39
51
  </head>
40
52
  <body>
41
53
  <div class="container-fluid" style="margin-top: 5px;">
42
- <div class="search-container">
43
- <div class="input-group">
44
- <input type="text" id="searchInput" class="form-control" placeholder="输入队列名称进行过滤...">
54
+ <div class="search-container" style="display: flex; align-items: center; margin-bottom: 10px;">
55
+ <div class="input-group" style="width: 400px;">
56
+ <input type="text" id="searchInput" class="form-control" placeholder="输入队列名称进行过滤..." style="width: 100%;">
45
57
  <span class="input-group-btn">
46
58
  <button class="btn btn-default" type="button" onclick="clearSearch()">
47
59
  <i class="glyphicon glyphicon-remove"></i>
48
60
  </button>
49
61
  </span>
50
62
  </div>
63
+ <button id="refresh-all-msg-counts" class="btn btn-info" style="margin-left: 30px;">更新所有队列的消息数量</button>
64
+ <button id="toggle-auto-refresh" class="btn btn-success" style="margin-left: 10px;">启动自动刷新</button>
65
+ <button id="show-explanation-btn" class="btn btn-default" style="margin-left: 10px;">说明</button>
51
66
  </div>
52
67
  <div id="queue-table"></div>
53
68
  </div>
54
69
 
70
+ <!-- Chart Modal -->
71
+ <div class="modal fade" id="chartModal" tabindex="-1" role="dialog" aria-labelledby="chartModalLabel" aria-hidden="true">
72
+ <div class="modal-dialog modal-lg modal-xl-custom" role="document">
73
+ <div class="modal-content">
74
+ <div class="modal-header">
75
+ <h4 class="modal-title" id="chartModalLabel">队列数据曲线图: <span id="chartQueueName"></span></h4>
76
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
77
+ </div>
78
+ <div class="modal-body">
79
+ <canvas id="queueDataChart"></canvas>
80
+ </div>
81
+ <div class="modal-footer">
82
+ <button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
83
+ </div>
84
+ </div>
85
+ </div>
86
+ </div>
87
+
88
+ <!-- Explanation Modal -->
89
+ <div class="modal fade" id="explanationModal" tabindex="-1" role="dialog" aria-labelledby="explanationModalLabel" aria-hidden="true">
90
+ <div class="modal-dialog" role="document">
91
+ <div class="modal-content">
92
+ <div class="modal-header">
93
+ <h4 class="modal-title" id="explanationModalLabel">说明</h4>
94
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
95
+ </div>
96
+ <div class="modal-body">
97
+ <ul id="explanation-text">
98
+ {# Content will be added by JavaScript #}
99
+ </ul>
100
+ </div>
101
+ <div class="modal-footer">
102
+ <button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
103
+ </div>
104
+ </div>
105
+ </div>
106
+ </div>
107
+
55
108
 
56
109
  <script>
57
110
  // 创建表格实例
58
111
  var table = new Tabulator("#queue-table", {
59
112
  theme: "bootstrap3",
60
113
  ajaxURL: "/queue/params_and_active_consumers",
61
- layout: "fitDataTable",
114
+ layout: "fitDataFill",
62
115
  responsiveLayout: false,
63
116
  pagination: true,
64
117
  paginationSize: 1000,
65
118
  height: "auto",
66
119
  locale: true,
120
+ rowFormatter: function(row) {
121
+ var data = row.getData();
122
+ var cell = row.getCell("queue_name");
123
+
124
+ if (cell && cell.getElement()) {
125
+ var element = cell.getElement();
126
+ if (data.consumer_count > 0) {
127
+ element.style.backgroundColor = "#4CAF50"; // 恢复绿色背景
128
+ element.style.color = "white";
129
+ } else {
130
+ element.style.backgroundColor = "#F44336"; // 恢复红色背景
131
+ element.style.color = "white";
132
+ }
133
+ }
134
+ },
67
135
  langs: {
68
136
  "zh-cn": {
69
137
  "pagination": {
@@ -79,20 +147,50 @@
79
147
  }
80
148
  },
81
149
  columns: [
82
- { title: "<br><br>队列名字", field: "queue_name", sorter: "string", headerSort: true, headerHozAlign: "center", hozAlign: "left", minWidth: 200, headerWordWrap: true },
150
+ {
151
+ title: "<br><br>队列名字",
152
+ field: "queue_name",
153
+ sorter: "string",
154
+ headerSort: true,
155
+ headerHozAlign: "center",
156
+ hozAlign: "left",
157
+ minWidth: 320, // 增加宽度以容纳按钮
158
+ headerWordWrap: true,
159
+ frozen: true,
160
+ formatter: function(cell, formatterParams, onRendered) {
161
+ const queueName = cell.getValue();
162
+ // 根据 isAutoRefreshing 状态动态设置按钮的显示样式
163
+ const buttonDisplay = isAutoRefreshing ? 'inline-block' : 'none';
164
+ return `<div style="display: flex; justify-content: space-between; align-items: center;">
165
+ <span>${queueName}</span>
166
+ <button class="btn btn-xs btn-info view-chart-btn"
167
+ data-queue-name="${queueName}"
168
+ style="margin-left: 10px; display: ${buttonDisplay};"
169
+ onclick="showQueueChart('${queueName}')">
170
+ <i class="glyphicon glyphicon-stats"></i> 查看曲线图
171
+ </button>
172
+ </div>`;
173
+ }
174
+ },
175
+
83
176
  { title: "<br>broker<br>类型", field: "broker_kind", sorter: "string" },
177
+ { title: "<br>消费<br>函数", field: "consuming_function_name", sorter: "string" },
178
+ { title: "<br>历史运<br>行次数", field: "history_run_count", sorter: "number", width: 150 },
179
+ { title: "<br>历史运<br>行失败<br>次数", field: "history_run_fail_count", sorter: "number", width: 150 },
84
180
  { title: "<br>近10秒<br>完成", field: "all_consumers_last_x_s_execute_count", sorter: "number", width: 100 },
85
181
  { title: "<br>近10秒<br>失败", field: "all_consumers_last_x_s_execute_count_fail", sorter: "number", width: 100 },
86
182
 
87
183
  { title: "近10秒<br>函数运行<br>平均耗时", field: "all_consumers_last_x_s_avarage_function_spend_time", sorter: "number", width: 100 },
88
184
  { title: "累计<br>函数运行<br>平均耗时", field: "all_consumers_avarage_function_spend_time_from_start", sorter: "number", width: 100 },
89
185
 
90
- { title: "<br><br>消息数量", field: "msg_count", sorter: "number", width: 170,
186
+ { title: "<br><br>消息数量", field: "msg_count", sorter: "number", width: 250,
91
187
  formatter: function(cell) {
92
188
  const row = cell.getRow().getData();
189
+ const initialCount = cell.getValue() === null ? '' : cell.getValue();
190
+ const initialCountStr = initialCount === '' ? '0' : String(initialCount); // Ensure '0' for empty initial
93
191
  return `
94
- <div style="display: flex; align-items: center; justify-content: space-between; width: 100%; padding-right: 30px;">
95
- <span id="msg-count-${row.queue_name}" style="min-width: 70px; text-align: right; padding-right: 25px;">${cell.getValue() === null ? '' : cell.getValue()}</span>
192
+ <div style="display: flex; align-items: center; justify-content: space-between; width: 100%; padding-right: 10px;">
193
+ <span id="msg-count-${row.queue_name}" data-last-count="${initialCountStr}" style="min-width: 70px; text-align: right; padding-right: 25px;">${initialCount}</span>
96
194
  <button class="btn btn-primary btn-sm" onclick="getMessageCount('${row.queue_name}')">获取</button>
97
195
  </div>
98
196
  `;
@@ -122,11 +220,15 @@
122
220
  },
123
221
  {
124
222
  title: "<br><br>操作",
125
- width: 400,
223
+ width: 500,
126
224
  formatter: function(cell) {
127
225
  const row = cell.getRow().getData();
226
+ const btnId = 'showParamsBtn_' + Math.random().toString(36).substr(2, 9);
227
+ setTimeout(() => {
228
+ document.getElementById(btnId)?.addEventListener('click', () => showParams(row.queue_params));
229
+ }, 0);
128
230
  return `
129
- <button class="btn btn-info btn-sm action-btn" onclick='showParams(${JSON.stringify(row.queue_params)})'>查看消费者配置</button>
231
+ <button id="${btnId}" class="btn btn-info btn-sm action-btn">查看消费者配置</button>
130
232
  <button class="btn btn-danger btn-sm action-btn" onclick="clearQueue('${row.queue_name}')">清空队列消息</button>
131
233
  <button class="btn btn-warning btn-sm action-btn" onclick="pauseConsume('${row.queue_name}')">暂停消费</button>
132
234
  <button class="btn btn-success btn-sm action-btn" onclick="resumeConsume('${row.queue_name}')">恢复消费</button>
@@ -136,12 +238,16 @@
136
238
  ],
137
239
  ajaxResponse: function(url, params, response) {
138
240
  // 转换API响应为表格数据
139
- return Object.entries(response).map(([queue_name, data]) => ({
241
+ const tableData = Object.entries(response).map(([queue_name, data]) => ({
140
242
  queue_name: queue_name,
243
+
141
244
  broker_kind: data.queue_params.broker_kind,
245
+ consuming_function_name: data.queue_params.consuming_function_name,
246
+ history_run_count: data.history_run_count,
247
+ history_run_fail_count: data.history_run_fail_count,
142
248
  all_consumers_last_x_s_execute_count: data.all_consumers_last_x_s_execute_count,
143
249
  all_consumers_last_x_s_execute_count_fail: data.all_consumers_last_x_s_execute_count_fail,
144
- msg_count: data.msg_num_in_broker, // 初始化为空字符串
250
+ msg_count: data.msg_num_in_broker,
145
251
  consumer_count: data.active_consumers.length,
146
252
  active_consumers: data.active_consumers,
147
253
  queue_params: data.queue_params,
@@ -149,6 +255,18 @@
149
255
  all_consumers_last_x_s_avarage_function_spend_time:data.all_consumers_last_x_s_avarage_function_spend_time,
150
256
  all_consumers_avarage_function_spend_time_from_start:data.all_consumers_avarage_function_spend_time_from_start
151
257
  }));
258
+
259
+ const processedTableDataForHistory = tableData.map(item => {
260
+ // 确保 msg_count 用于历史记录时是数字
261
+ const numericMsgCount = parseFloat(item.msg_count);
262
+ return {
263
+ ...item,
264
+ msg_count_numeric: isNaN(numericMsgCount) ? 0 : numericMsgCount
265
+ };
266
+ });
267
+ updateHistoricalData(processedTableDataForHistory);
268
+
269
+ return tableData;
152
270
  },
153
271
  });
154
272
 
@@ -206,16 +324,43 @@
206
324
  return;
207
325
  }
208
326
  const broker_kind = row.getData().broker_kind;
327
+
328
+ let countSpan = document.getElementById(`msg-count-${queueName}`);
329
+ let previous_count_str = countSpan.getAttribute('data-last-count') || '0';
330
+ let previous_count = parseInt(previous_count_str);
331
+ if (isNaN(previous_count)) previous_count = 0; // Fallback
332
+
333
+ countSpan.innerHTML = '正在获取...'; // Add a loading indicator
334
+
209
335
  // 获取消息数量的API调用
210
336
  $.get(`/queue/message_count/${broker_kind}/${queueName}`, function(response) {
211
337
  if (response.success) {
212
- // 更新表格中的消息数量
213
- row.update({msg_count: response.count});
214
- // 直接更新显示的数字
215
- alert(`获取 ${queueName} 队列 消息数量成功`);
216
- document.getElementById(`msg-count-${queueName}`).textContent = response.count;
217
-
338
+ const new_count = parseInt(response.count);
339
+ if (isNaN(new_count)) {
340
+ countSpan.innerHTML = 'get_msg_num_error';
341
+ countSpan.setAttribute('data-last-count', '0'); // Reset last count on error
342
+ return;
343
+ }
344
+
345
+ const difference = new_count - previous_count;
346
+ let diff_display_html = '';
347
+ if (countSpan.getAttribute('data-last-count') !== '0' || previous_count_str !== '') { // Only show diff if not initial load or previous was not error
348
+ if (difference > 0) {
349
+ diff_display_html = ` <span style="color: red;">↑ +${difference}</span>`;
350
+ } else if (difference < 0) {
351
+ diff_display_html = ` <span style="color: green;">↓ ${difference}</span>`;
352
+ }
353
+ }
354
+
355
+ countSpan.innerHTML = `${new_count}${diff_display_html}`;
356
+ countSpan.setAttribute('data-last-count', new_count.toString());
357
+ } else {
358
+ countSpan.innerHTML = 'get_msg_num_error';
359
+ countSpan.setAttribute('data-last-count', '0'); // Reset last count on error
218
360
  }
361
+ }).fail(function() {
362
+ countSpan.innerHTML = 'get_msg_num_error';
363
+ countSpan.setAttribute('data-last-count', '0'); // Reset last count on error
219
364
  });
220
365
  }
221
366
 
@@ -271,8 +416,9 @@
271
416
  <tr>
272
417
  <td>${consumer.computer_ip}</td>
273
418
  <td>${consumer.computer_name}</td>
274
- <td>${consumer.hearbeat_datetime_str}</td>
275
419
  <td>${consumer.process_id}</td>
420
+ <td>${consumer.hearbeat_datetime_str}</td>
421
+
276
422
  <td>${consumer.start_datetime_str}</td>
277
423
 
278
424
  <td>${consumer.last_x_s_execute_count}</td>
@@ -282,6 +428,7 @@
282
428
  <td>${consumer.total_consume_count_from_start_fail}</td>
283
429
  <td>${consumer.avarage_function_spend_time_from_start}</td>
284
430
  <td>${consumer.code_filename}</td>,
431
+ <td>${consumer.consumer_uuid}</td>
285
432
  </tr>
286
433
  `;
287
434
  });
@@ -301,8 +448,9 @@
301
448
  <tr>
302
449
  <th>计算机IP</th>
303
450
  <th>计算机名称</th>
304
- <th>最后心跳时间</th>
305
451
  <th>进程ID</th>
452
+ <th>最后心跳时间</th>
453
+
306
454
  <th>启动时间</th>
307
455
 
308
456
  <th>近10秒<br>运行完成<br>消息个数</th>
@@ -312,6 +460,7 @@
312
460
  <th>累计<br>运行失败<br>消息个数</th>
313
461
  <th>累计<br>函数运行<br>平均耗时</th>
314
462
  <th>代码文件名</th>
463
+ <th>消费者UUID</th>
315
464
  </tr>
316
465
  </thead>
317
466
  <tbody>
@@ -342,10 +491,252 @@
342
491
  });
343
492
  }
344
493
 
345
- // 定时刷新表格数据
346
- // setInterval(function() {
347
- // table.refreshData();
348
- // }, 5000);
494
+ document.getElementById("refresh-all-msg-counts").onclick = function() {
495
+ table.getRows().forEach(row => {
496
+ const queueName = row.getData().queue_name;
497
+ getMessageCount(queueName);
498
+ });
499
+ };
500
+
501
+ // --- BEGIN NEW SCRIPT LOGIC ---
502
+ let isAutoRefreshing = false;
503
+ let autoRefreshIntervalId = null;
504
+ const AUTO_REFRESH_INTERVAL = 10000; // 10 秒
505
+ let historicalData = {}; // 结构: { queueName: [{timestamp: Date.now(), history_run_count: val, ...}, ...], ... }
506
+ const MAX_DATA_AGE_MS = 60 * 60 * 1000; // 1 小时
507
+
508
+ // 定义需要记录并展示在图表中的列字段及其显示名称
509
+ const CHARTABLE_FIELDS_MAP = {
510
+ "history_run_count": "历史运行次数",
511
+ "history_run_fail_count": "历史运行失败次数",
512
+ "all_consumers_last_x_s_execute_count": "近10秒完成",
513
+ "all_consumers_last_x_s_execute_count_fail": "近10秒失败",
514
+ "all_consumers_last_x_s_avarage_function_spend_time": "近10秒函数运行平均耗时",
515
+ "all_consumers_avarage_function_spend_time_from_start": "累计函数运行平均耗时",
516
+ "msg_count_numeric": "消息数量" // 注意:这是我们将在ajaxResponse中处理得到的数值型消息数量
517
+ };
518
+ let chartInstance = null; // 用于存储Chart.js的实例
519
+ let activeChartQueueName = null; // 新增:跟踪当前活动图表的队列名称
520
+
521
+ // 新增:预定义的颜色数组,用于图表线条 (更新后的高区分度颜色)
522
+ const PREDEFINED_COLORS = [
523
+ '#E60012', // 鲜红 (Red)
524
+ '#005AC8', // 鲜蓝 (Blue)
525
+ '#00A600', // 鲜绿 (Green)
526
+ '#FF9900', // 亮橙 (Orange)
527
+ '#8B28B7', // 紫色 (Purple)
528
+ '#9A6324', // 棕色 (Brown)
529
+ '#5E8C78', // 暗青色 (Teal)
530
+ '#F58231', // 亮橙红 (Vermilion)
531
+ '#42D4F4', // 天蓝色 (Cyan)
532
+ '#BF6131', // 锈红色 (Rust)
533
+ '#3CB44B', // 另一种绿色 (Lime Green)
534
+ '#4363D8', // 另一种蓝色 (Indigo)
535
+ '#F032E6', // 品红色 (Magenta)
536
+ '#BCF60C', // 黄绿色 (Chartreuse)
537
+ '#FABEBE', // 浅粉色 (Light Pink)
538
+ '#AAFFC3', // 浅绿色 (Mint Green)
539
+ '#E6BEFF', // 浅紫色 (Lavender)
540
+ '#FFFAC8' // 浅黄色 (Cream)
541
+ ];
542
+
543
+ function updateHistoricalData(newDataFromTable) {
544
+ const now = Date.now();
545
+ newDataFromTable.forEach(item => {
546
+ const queueName = item.queue_name;
547
+ if (!historicalData[queueName]) {
548
+ historicalData[queueName] = [];
549
+ }
550
+
551
+ let dataPoint = { timestamp: now };
552
+ Object.keys(CHARTABLE_FIELDS_MAP).forEach(fieldKey => {
553
+ dataPoint[fieldKey] = item[fieldKey]; // item已经包含了转换后的msg_count_numeric
554
+ });
555
+ historicalData[queueName].push(dataPoint);
556
+
557
+ // 清理超过1小时的旧数据
558
+ historicalData[queueName] = historicalData[queueName].filter(dp => (now - dp.timestamp) <= MAX_DATA_AGE_MS);
559
+ });
560
+ updateActiveChartIfVisible(); // 在历史数据更新后,尝试更新活动图表
561
+ }
562
+
563
+ function refreshTableData() {
564
+ console.log("Auto-refresh: refreshing table data...");
565
+ table.replaceData() // replaceData会重新调用ajaxURL,进而触发ajaxResponse中的updateHistoricalData
566
+ .then(() => {
567
+ console.log("Auto-refresh: table data refreshed successfully.");
568
+ })
569
+ .catch(error => {
570
+ console.error("Auto-refresh: error refreshing table data:", error);
571
+ // 可以考虑在这里停止自动刷新或通知用户
572
+ });
573
+ }
574
+
575
+ function toggleAutoRefresh() {
576
+ const button = document.getElementById("toggle-auto-refresh");
577
+ // const chartButtons = document.querySelectorAll(".view-chart-btn"); // 不再直接通过此方式控制显隐
578
+
579
+ if (isAutoRefreshing) { // 如果正在刷新,则停止
580
+ clearInterval(autoRefreshIntervalId);
581
+ isAutoRefreshing = false;
582
+ button.textContent = "启动自动刷新";
583
+ button.classList.remove("btn-danger");
584
+ button.classList.add("btn-success");
585
+ // chartButtons.forEach(btn => btn.style.display = 'none'); // Formatter将处理
586
+ console.log("Auto-refresh stopped.");
587
+ if (table) {
588
+ table.redraw(true); // 强制表格重绘,formatter会根据isAutoRefreshing=false隐藏按钮
589
+ }
590
+ } else { // 如果未刷新,则启动
591
+ isAutoRefreshing = true;
592
+ button.textContent = "暂停自动刷新";
593
+ button.classList.remove("btn-success");
594
+ button.classList.add("btn-danger");
595
+ // chartButtons.forEach(btn => btn.style.display = 'inline-block'); // Formatter将处理
596
+
597
+ refreshTableData(); // 立即执行一次刷新, 这会调用replaceData, 触发重绘, formatter会显示按钮
598
+ autoRefreshIntervalId = setInterval(refreshTableData, AUTO_REFRESH_INTERVAL);
599
+ console.log("Auto-refresh started. Interval ID:", autoRefreshIntervalId);
600
+ }
601
+ }
602
+
603
+ document.getElementById("toggle-auto-refresh").addEventListener("click", toggleAutoRefresh);
604
+
605
+ // 这个函数由表格中每行的"查看图表"按钮调用
606
+ function showQueueChart(queueName) {
607
+ const dataForChart = historicalData[queueName] || [];
608
+ document.getElementById("chartQueueName").textContent = queueName; // 设置模态框标题中的队列名
609
+ activeChartQueueName = queueName; // 设置当前活动的图表队列名
610
+
611
+ if (chartInstance) {
612
+ chartInstance.destroy(); // 销毁已存在的图表实例,避免重复渲染
613
+ }
614
+
615
+ const chartCanvas = document.getElementById('queueDataChart');
616
+ const ctx = chartCanvas.getContext('2d');
617
+
618
+ if (dataForChart.length === 0) {
619
+ // alert("队列 " + queueName + " 暂无历史数据可供展示。请等待自动刷新收集更多数据。");
620
+ // 可以在canvas上绘制提示信息
621
+ ctx.clearRect(0, 0, chartCanvas.width, chartCanvas.height); // 清空画布
622
+ ctx.font = "16px Arial";
623
+ ctx.textAlign = "center";
624
+ ctx.fillText("队列 " + queueName + " 暂无历史数据,请等待数据收集。", chartCanvas.width / 2, chartCanvas.height / 2);
625
+ $('#chartModal').modal('show');
626
+ return;
627
+ }
628
+
629
+ const labels = dataForChart.map(dp => new Date(dp.timestamp).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', second: '2-digit' }));
630
+
631
+ const datasets = Object.keys(CHARTABLE_FIELDS_MAP).map((fieldKey, index) => {
632
+ const displayName = CHARTABLE_FIELDS_MAP[fieldKey];
633
+
634
+ // 设置默认显示或隐藏
635
+ const isDefaultVisible = fieldKey === 'all_consumers_last_x_s_execute_count' || fieldKey === 'all_consumers_last_x_s_execute_count_fail';
636
+
637
+ return {
638
+ label: displayName,
639
+ data: dataForChart.map(dp => dp[fieldKey] === undefined || dp[fieldKey] === null ? NaN : dp[fieldKey]), // 处理可能存在的undefined或null
640
+ fill: false,
641
+ borderColor: PREDEFINED_COLORS[index % PREDEFINED_COLORS.length], // 使用预定义颜色
642
+ backgroundColor: PREDEFINED_COLORS[index % PREDEFINED_COLORS.length] + '33', // 面积图或点的背景颜色(带透明度)
643
+ tension: 0.1,
644
+ pointRadius: 3, // 点的大小
645
+ pointHoverRadius: 5, // 鼠标悬浮时点的大小
646
+ borderWidth: 2, // 线条宽度
647
+ hidden: !isDefaultVisible // 控制默认是否隐藏
648
+ };
649
+ });
650
+
651
+ chartInstance = new Chart(ctx, {
652
+ type: 'line',
653
+ data: {
654
+ labels: labels,
655
+ datasets: datasets
656
+ },
657
+ options: {
658
+ responsive: true,
659
+ maintainAspectRatio: false, // 允许图表填充模态框主体
660
+ scales: {
661
+ x: {
662
+ title: {
663
+ display: true,
664
+ text: '时间'
665
+ }
666
+ },
667
+ y: {
668
+ beginAtZero: false, // Y轴不一定从0开始,根据数据自适应
669
+ title: {
670
+ display: true,
671
+ text: '数值'
672
+ }
673
+ }
674
+ },
675
+ plugins: {
676
+ legend: {
677
+ position: 'top',
678
+ },
679
+ title: {
680
+ display: true,
681
+ text: `队列 [${queueName}] 各项指标变化趋势`
682
+ },
683
+ tooltip: {
684
+ mode: 'index',
685
+ intersect: false,
686
+ }
687
+ },
688
+ interaction: { // 增强交互性
689
+ mode: 'nearest',
690
+ axis: 'x',
691
+ intersect: false
692
+ }
693
+ }
694
+ });
695
+ $('#chartModal').modal('show'); // 显示模态框
696
+ }
697
+
698
+ // 新增:模态框关闭时的处理
699
+ $('#chartModal').on('hidden.bs.modal', function () {
700
+ if (chartInstance) {
701
+ chartInstance.destroy();
702
+ chartInstance = null;
703
+ }
704
+ activeChartQueueName = null; // 清除活动图表队列名
705
+ console.log("Chart modal closed and instance destroyed.");
706
+ });
707
+
708
+ // 新增:如果图表可见,则用最新数据更新它
709
+ function updateActiveChartIfVisible() {
710
+ if (activeChartQueueName && chartInstance && historicalData[activeChartQueueName]) {
711
+ const dataForChart = historicalData[activeChartQueueName];
712
+ if (dataForChart.length > 0) {
713
+ const newLabels = dataForChart.map(dp => new Date(dp.timestamp).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', second: '2-digit' }));
714
+
715
+ chartInstance.data.labels = newLabels;
716
+ Object.keys(CHARTABLE_FIELDS_MAP).forEach((fieldKey, index) => {
717
+ if (chartInstance.data.datasets[index]) {
718
+ chartInstance.data.datasets[index].data = dataForChart.map(dp => dp[fieldKey] === undefined || dp[fieldKey] === null ? NaN : dp[fieldKey]);
719
+ }
720
+ });
721
+ chartInstance.update(); // 更新图表
722
+ console.log(`Chart for ${activeChartQueueName} updated dynamically.`);
723
+ }
724
+ }
725
+ }
726
+
727
+ // 新增:显示说明模态框的函数
728
+ function showExplanationModal() {
729
+ const explanationTextHtml = `
730
+ <li>消息队列的各项指标数据是 funboost 消费者每隔10秒周期上报到 redis 的,所以不是毫秒级实时,而是10秒级实时。</li>
731
+ <li>"更新所有队列消息数量"按钮和表格"消息数量"列的获取按钮,是实时查询 broker 的消息数量,不是基于消费者上报到 redis 的数据。(因为有的队列可能没有启动相应的消费者,也就没有上报方)</li>
732
+ <li>点击"启动自动刷新"按钮后,会每隔10秒更新表格的数据,并在前端内存中维护近1小时内的各种指标数据;同时,"消息队列"这一列的每行会出现"查看曲线图"按钮,点击后弹出的曲线图会动态显示内存中保存的指标数据变化趋势。</li>
733
+ `;
734
+ document.getElementById('explanation-text').innerHTML = explanationTextHtml;
735
+ $('#explanationModal').modal('show');
736
+ }
737
+
738
+ // 绑定说明按钮的点击事件
739
+ document.getElementById('show-explanation-btn').addEventListener('click', showExplanationModal);
349
740
  </script>
350
741
  </body>
351
742
  </html>