jettask 0.2.1__py3-none-any.whl → 0.2.4__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 (89) hide show
  1. jettask/constants.py +213 -0
  2. jettask/core/app.py +525 -205
  3. jettask/core/cli.py +193 -185
  4. jettask/core/consumer_manager.py +126 -34
  5. jettask/core/context.py +3 -0
  6. jettask/core/enums.py +137 -0
  7. jettask/core/event_pool.py +501 -168
  8. jettask/core/message.py +147 -0
  9. jettask/core/offline_worker_recovery.py +181 -114
  10. jettask/core/task.py +10 -174
  11. jettask/core/task_batch.py +153 -0
  12. jettask/core/unified_manager_base.py +243 -0
  13. jettask/core/worker_scanner.py +54 -54
  14. jettask/executors/asyncio.py +184 -64
  15. jettask/webui/backend/config.py +51 -0
  16. jettask/webui/backend/data_access.py +2083 -92
  17. jettask/webui/backend/data_api.py +3294 -0
  18. jettask/webui/backend/dependencies.py +261 -0
  19. jettask/webui/backend/init_meta_db.py +158 -0
  20. jettask/webui/backend/main.py +1358 -69
  21. jettask/webui/backend/main_unified.py +78 -0
  22. jettask/webui/backend/main_v2.py +394 -0
  23. jettask/webui/backend/namespace_api.py +295 -0
  24. jettask/webui/backend/namespace_api_old.py +294 -0
  25. jettask/webui/backend/namespace_data_access.py +611 -0
  26. jettask/webui/backend/queue_backlog_api.py +727 -0
  27. jettask/webui/backend/queue_stats_v2.py +521 -0
  28. jettask/webui/backend/redis_monitor_api.py +476 -0
  29. jettask/webui/backend/unified_api_router.py +1601 -0
  30. jettask/webui/db_init.py +204 -32
  31. jettask/webui/frontend/package-lock.json +492 -1
  32. jettask/webui/frontend/package.json +4 -1
  33. jettask/webui/frontend/src/App.css +105 -7
  34. jettask/webui/frontend/src/App.jsx +49 -20
  35. jettask/webui/frontend/src/components/NamespaceSelector.jsx +166 -0
  36. jettask/webui/frontend/src/components/QueueBacklogChart.jsx +298 -0
  37. jettask/webui/frontend/src/components/QueueBacklogTrend.jsx +638 -0
  38. jettask/webui/frontend/src/components/QueueDetailsTable.css +65 -0
  39. jettask/webui/frontend/src/components/QueueDetailsTable.jsx +487 -0
  40. jettask/webui/frontend/src/components/QueueDetailsTableV2.jsx +465 -0
  41. jettask/webui/frontend/src/components/ScheduledTaskFilter.jsx +423 -0
  42. jettask/webui/frontend/src/components/TaskFilter.jsx +425 -0
  43. jettask/webui/frontend/src/components/TimeRangeSelector.css +21 -0
  44. jettask/webui/frontend/src/components/TimeRangeSelector.jsx +160 -0
  45. jettask/webui/frontend/src/components/layout/AppLayout.css +95 -0
  46. jettask/webui/frontend/src/components/layout/AppLayout.jsx +49 -0
  47. jettask/webui/frontend/src/components/layout/Header.css +34 -10
  48. jettask/webui/frontend/src/components/layout/Header.jsx +31 -23
  49. jettask/webui/frontend/src/components/layout/SideMenu.css +137 -0
  50. jettask/webui/frontend/src/components/layout/SideMenu.jsx +209 -0
  51. jettask/webui/frontend/src/components/layout/TabsNav.css +244 -0
  52. jettask/webui/frontend/src/components/layout/TabsNav.jsx +206 -0
  53. jettask/webui/frontend/src/components/layout/UserInfo.css +197 -0
  54. jettask/webui/frontend/src/components/layout/UserInfo.jsx +197 -0
  55. jettask/webui/frontend/src/contexts/NamespaceContext.jsx +72 -0
  56. jettask/webui/frontend/src/contexts/TabsContext.backup.jsx +245 -0
  57. jettask/webui/frontend/src/main.jsx +1 -0
  58. jettask/webui/frontend/src/pages/Alerts.jsx +684 -0
  59. jettask/webui/frontend/src/pages/Dashboard.jsx +1330 -0
  60. jettask/webui/frontend/src/pages/QueueDetail.jsx +1109 -10
  61. jettask/webui/frontend/src/pages/QueueMonitor.jsx +236 -115
  62. jettask/webui/frontend/src/pages/Queues.jsx +5 -1
  63. jettask/webui/frontend/src/pages/ScheduledTasks.jsx +809 -0
  64. jettask/webui/frontend/src/pages/Settings.jsx +800 -0
  65. jettask/webui/frontend/src/services/api.js +7 -5
  66. jettask/webui/frontend/src/utils/suppressWarnings.js +22 -0
  67. jettask/webui/frontend/src/utils/userPreferences.js +154 -0
  68. jettask/webui/multi_namespace_consumer.py +543 -0
  69. jettask/webui/pg_consumer.py +983 -246
  70. jettask/webui/static/dist/assets/index-7129cfe1.css +1 -0
  71. jettask/webui/static/dist/assets/index-8d1935cc.js +774 -0
  72. jettask/webui/static/dist/index.html +2 -2
  73. jettask/webui/task_center.py +216 -0
  74. jettask/webui/task_center_client.py +150 -0
  75. jettask/webui/unified_consumer_manager.py +193 -0
  76. {jettask-0.2.1.dist-info → jettask-0.2.4.dist-info}/METADATA +1 -1
  77. jettask-0.2.4.dist-info/RECORD +134 -0
  78. jettask/webui/pg_consumer_slow.py +0 -1099
  79. jettask/webui/pg_consumer_test.py +0 -678
  80. jettask/webui/static/dist/assets/index-823408e8.css +0 -1
  81. jettask/webui/static/dist/assets/index-9968b0b8.js +0 -543
  82. jettask/webui/test_pg_consumer_recovery.py +0 -547
  83. jettask/webui/test_recovery_simple.py +0 -492
  84. jettask/webui/test_self_recovery.py +0 -467
  85. jettask-0.2.1.dist-info/RECORD +0 -91
  86. {jettask-0.2.1.dist-info → jettask-0.2.4.dist-info}/WHEEL +0 -0
  87. {jettask-0.2.1.dist-info → jettask-0.2.4.dist-info}/entry_points.txt +0 -0
  88. {jettask-0.2.1.dist-info → jettask-0.2.4.dist-info}/licenses/LICENSE +0 -0
  89. {jettask-0.2.1.dist-info → jettask-0.2.4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,465 @@
1
+ import { useState, useEffect, forwardRef, useImperativeHandle, useRef } from 'react';
2
+ import { Tag, message, Button, Space, Progress, Tooltip } from 'antd';
3
+ import { ProTable } from '@ant-design/pro-components';
4
+ import {
5
+ TeamOutlined,
6
+ ThunderboltOutlined,
7
+ CheckCircleOutlined,
8
+ CloseCircleOutlined,
9
+ SyncOutlined,
10
+ EyeOutlined,
11
+ ExpandOutlined,
12
+ CompressOutlined,
13
+ UserOutlined,
14
+ CopyOutlined
15
+ } from '@ant-design/icons';
16
+ import { useNavigate } from 'react-router-dom';
17
+ import axios from 'axios';
18
+ import dayjs from 'dayjs';
19
+ import { useNamespace } from '../contexts/NamespaceContext';
20
+ import './QueueDetailsTable.css';
21
+
22
+ const QueueDetailsTableV2 = forwardRef(({
23
+ autoRefresh = false,
24
+ refreshInterval = 5000,
25
+ selectedQueues = [],
26
+ timeRange = '15m',
27
+ customTimeRange = null
28
+ }, ref) => {
29
+ const navigate = useNavigate();
30
+ const { currentNamespace } = useNamespace();
31
+ const [loading, setLoading] = useState(false);
32
+ const [data, setData] = useState([]);
33
+ const [expandedRowKeys, setExpandedRowKeys] = useState([]);
34
+ const actionRef = useRef();
35
+
36
+ // 暴露刷新方法给父组件
37
+ useImperativeHandle(ref, () => ({
38
+ refresh: fetchQueueStats
39
+ }));
40
+
41
+ // 复制文本到剪贴板
42
+ const handleCopy = async (text) => {
43
+ try {
44
+ await navigator.clipboard.writeText(text);
45
+ message.success('已复制到剪贴板');
46
+ } catch (err) {
47
+ message.error('复制失败');
48
+ }
49
+ };
50
+
51
+ // 获取队列统计信息(使用新的v2 API)
52
+ const fetchQueueStats = async () => {
53
+ if (!currentNamespace) {
54
+ return;
55
+ }
56
+
57
+ setLoading(true);
58
+ try {
59
+ // 构建查询参数
60
+ const params = new URLSearchParams();
61
+
62
+ // 添加时间范围参数
63
+ if (customTimeRange && customTimeRange.length === 2) {
64
+ // 自定义时间范围
65
+ // customTimeRange可能是dayjs对象或ISO字符串
66
+ const startTime = dayjs.isDayjs(customTimeRange[0])
67
+ ? customTimeRange[0].toISOString()
68
+ : customTimeRange[0];
69
+ const endTime = dayjs.isDayjs(customTimeRange[1])
70
+ ? customTimeRange[1].toISOString()
71
+ : customTimeRange[1];
72
+ params.append('start_time', startTime);
73
+ params.append('end_time', endTime);
74
+ } else if (timeRange && timeRange !== 'custom') {
75
+ // 预设时间范围
76
+ params.append('time_range', timeRange);
77
+ }
78
+
79
+ const url = `/api/v2/namespaces/${currentNamespace}/queues/stats${params.toString() ? '?' + params.toString() : ''}`;
80
+ console.log('Fetching queue stats with URL:', url);
81
+
82
+ const response = await axios.get(url);
83
+ if (response.data.success) {
84
+ // 过滤选中的队列
85
+ let queuesData = response.data.data;
86
+ if (selectedQueues && selectedQueues.length > 0) {
87
+ queuesData = queuesData.filter(q => selectedQueues.includes(q.queue_name));
88
+ }
89
+ setData(queuesData);
90
+ } else {
91
+ message.error('获取队列详情失败');
92
+ }
93
+ } catch (error) {
94
+ console.error('Failed to fetch queue stats:', error);
95
+ message.error('获取队列详情失败');
96
+ } finally {
97
+ setLoading(false);
98
+ }
99
+ };
100
+
101
+ // 初始化加载数据
102
+ useEffect(() => {
103
+ if (selectedQueues && selectedQueues.length > 0) {
104
+ fetchQueueStats();
105
+ } else {
106
+ setData([]);
107
+ }
108
+ }, [currentNamespace, selectedQueues, timeRange, customTimeRange]);
109
+
110
+ // 自动刷新
111
+ useEffect(() => {
112
+ if (autoRefresh) {
113
+ const timer = setInterval(() => {
114
+ fetchQueueStats();
115
+ }, refreshInterval);
116
+ return () => clearInterval(timer);
117
+ }
118
+ }, [autoRefresh, refreshInterval]);
119
+
120
+ // 消费者组表格列定义
121
+ const consumerGroupColumns = [
122
+ // {
123
+ // title: '消费者组',
124
+ // dataIndex: 'group_name',
125
+ // key: 'group_name',
126
+ // width: 220,
127
+ // ellipsis: {
128
+ // showTitle: false, // 不使用默认的 title,我们自定义
129
+ // },
130
+ // render: (_, record) => {
131
+ // // 直接从 record 获取原始数据
132
+ // const text = record.group_name || '';
133
+ // // 提取最后一部分(冒号分隔的最后一段)
134
+ // const lastPart = text.split(':').pop() || text;
135
+ // return (
136
+ // <div style={{ display: 'flex', alignItems: 'center', width: '100%' }}>
137
+ // <Tooltip title={text}>
138
+ // <span
139
+ // style={{
140
+ // cursor: 'pointer',
141
+ // overflow: 'hidden',
142
+ // textOverflow: 'ellipsis',
143
+ // whiteSpace: 'nowrap',
144
+ // flex: 1,
145
+ // minWidth: 0
146
+ // }}
147
+ // onClick={() => handleCopy(text)}
148
+ // >
149
+ // {lastPart}
150
+ // </span>
151
+ // </Tooltip>
152
+ // <Tooltip title="点击复制完整名称">
153
+ // <Button
154
+ // type="text"
155
+ // size="small"
156
+ // icon={<CopyOutlined />}
157
+ // onClick={() => handleCopy(text)}
158
+ // style={{ padding: '0 4px', flexShrink: 0 }}
159
+ // />
160
+ // </Tooltip>
161
+ // </div>
162
+ // );
163
+ // },
164
+ // },
165
+ {
166
+ title: '任务名',
167
+ dataIndex: 'task_name',
168
+ key: 'task_name',
169
+ width: 180,
170
+ ellipsis: true,
171
+ copyable: true,
172
+ },
173
+ {
174
+ title: '优先级',
175
+ dataIndex: 'priority',
176
+ key: 'priority',
177
+ width: 80,
178
+ align: 'center',
179
+ render: (priority) => {
180
+ let color;
181
+ if (priority === 0) {
182
+ color = 'default';
183
+ } else if (priority <= 3) {
184
+ color = 'red';
185
+ } else if (priority <= 6) {
186
+ color = 'orange';
187
+ } else {
188
+ color = 'green';
189
+ }
190
+
191
+ return (
192
+ <Tag color={color} style={{ fontSize: '12px', fontWeight: 'bold' }}>
193
+ {priority}
194
+ </Tag>
195
+ );
196
+ }
197
+ },
198
+ {
199
+ title: '待处理',
200
+ dataIndex: 'pending',
201
+ key: 'pending',
202
+ width: 100,
203
+ align: 'center',
204
+ render: (pending) => (
205
+ <span>{pending ? pending : 0}</span>
206
+ // <Badge count={pending} style={{ backgroundColor: pending > 0 ? '#fa8c16' : '#d9d9d9' }} overflowCount={9999} />
207
+ )
208
+ },
209
+ {
210
+ title: '可见/不可见',
211
+ key: 'messages',
212
+ width: 140,
213
+ align: 'center',
214
+ render: (_, record) => (
215
+ <Space>
216
+ <span style={{ color: '#1890ff' }}>{record.visible_messages}</span>
217
+ <span>/</span>
218
+ <span style={{ color: '#8c8c8c' }}>{record.invisible_messages}</span>
219
+ </Space>
220
+ )
221
+ },
222
+ {
223
+ title: '成功/失败',
224
+ key: 'stats',
225
+ width: 120,
226
+ align: 'center',
227
+ render: (_, record) => (
228
+ <Space>
229
+ <span style={{ color: '#52c41a' }}>{record.success_count}</span>
230
+ <span>/</span>
231
+ <span style={{ color: '#ff4d4f' }}>{record.failed_count}</span>
232
+ </Space>
233
+ )
234
+ },
235
+ {
236
+ title: '成功率',
237
+ dataIndex: 'success_rate',
238
+ key: 'success_rate',
239
+ width: 120,
240
+ align: 'center',
241
+ render: (rate) => (
242
+ <Progress
243
+ percent={rate}
244
+ size="small"
245
+ strokeColor={rate >= 95 ? '#52c41a' : rate >= 80 ? '#faad14' : '#ff4d4f'}
246
+ />
247
+ )
248
+ },
249
+ {
250
+ title: '处理速率',
251
+ dataIndex: 'processing_rate',
252
+ key: 'processing_rate',
253
+ width: 100,
254
+ align: 'center',
255
+ render: (rate) => (
256
+ <span>
257
+ <ThunderboltOutlined style={{ color: '#faad14' }} /> {rate}/min
258
+ </span>
259
+ )
260
+ },
261
+ {
262
+ title: '平均耗时',
263
+ dataIndex: 'avg_execution_time',
264
+ key: 'avg_execution_time',
265
+ width: 100,
266
+ align: 'center',
267
+ render: (time) => (
268
+ <span>{time ? `${time.toFixed(2)}s` : '-'}</span>
269
+ )
270
+ }
271
+ ];
272
+
273
+ // 主表格列定义
274
+ const columns = [
275
+ {
276
+ title: '队列名',
277
+ dataIndex: 'queue_name',
278
+ key: 'queue_name',
279
+ width: 200,
280
+ fixed: 'left',
281
+ ellipsis: true,
282
+ copyable: true,
283
+ render: (text) => (
284
+ <span style={{ fontWeight: 'bold' }}>{text}</span>
285
+ )
286
+ },
287
+ {
288
+ title: '消费者组数',
289
+ dataIndex: 'consumer_groups_count',
290
+ key: 'consumer_groups_count',
291
+ width: 120,
292
+ align: 'center',
293
+ render: (count, record) => (
294
+ <Space>
295
+ <TeamOutlined />
296
+ <span>{count}</span>
297
+ {count > 0 && (
298
+ <Button
299
+ type="link"
300
+ size="small"
301
+ onClick={() => {
302
+ if (expandedRowKeys.includes(record.queue_name)) {
303
+ setExpandedRowKeys(expandedRowKeys.filter(key => key !== record.queue_name));
304
+ } else {
305
+ setExpandedRowKeys([...expandedRowKeys, record.queue_name]);
306
+ }
307
+ }}
308
+ >
309
+ {expandedRowKeys.includes(record.queue_name) ? '收起' : '展开'}
310
+ </Button>
311
+ )}
312
+ </Space>
313
+ )
314
+ },
315
+ {
316
+ title: '队列总长度',
317
+ dataIndex: 'total_length',
318
+ key: 'total_length',
319
+ width: 120,
320
+ align: 'center',
321
+ render: (length) => (
322
+ <span>{length ? length : 0}</span>
323
+ // <Badge
324
+ // count={length}
325
+ // style={{
326
+ // backgroundColor: length > 1000 ? '#ff4d4f' : length > 100 ? '#fa8c16' : '#52c41a'
327
+ // }}
328
+ // overflowCount={999999}
329
+ // />
330
+ )
331
+ },
332
+ {
333
+ title: '在线Workers',
334
+ dataIndex: 'active_workers',
335
+ key: 'active_workers',
336
+ width: 120,
337
+ align: 'center',
338
+ render: (workers) => (
339
+ <Space>
340
+ <UserOutlined style={{ color: workers > 0 ? '#52c41a' : '#d9d9d9' }} />
341
+ <span style={{ color: workers > 0 ? '#52c41a' : '#d9d9d9' }}>{workers}</span>
342
+ </Space>
343
+ )
344
+ },
345
+ {
346
+ title: '成功/失败',
347
+ key: 'success_failed',
348
+ width: 150,
349
+ align: 'center',
350
+ render: (_, record) => (
351
+ <Space>
352
+ <span style={{ color: '#52c41a' }}>{record.total_success}</span>
353
+ <span>/</span>
354
+ <span style={{ color: '#ff4d4f' }}>{record.total_failed}</span>
355
+ </Space>
356
+ )
357
+ },
358
+ {
359
+ title: '总体成功率',
360
+ dataIndex: 'overall_success_rate',
361
+ key: 'overall_success_rate',
362
+ width: 120,
363
+ align: 'center',
364
+ render: (rate) => (
365
+ <Progress
366
+ percent={rate}
367
+ size="small"
368
+ strokeColor={rate >= 95 ? '#52c41a' : rate >= 80 ? '#faad14' : '#ff4d4f'}
369
+ />
370
+ )
371
+ },
372
+ {
373
+ title: '操作',
374
+ key: 'action',
375
+ width: 100,
376
+ fixed: 'right',
377
+ align: 'center',
378
+ render: (_, record) => (
379
+ <Button
380
+ type="link"
381
+ icon={<EyeOutlined />}
382
+ onClick={() => {
383
+ navigate(`/queue/${record.queue_name}`);
384
+ }}
385
+ >
386
+ 查看
387
+ </Button>
388
+ )
389
+ }
390
+ ];
391
+
392
+ // 展开行渲染
393
+ const expandedRowRender = (record) => {
394
+ return (
395
+ <ProTable
396
+ columns={consumerGroupColumns}
397
+ dataSource={record.consumer_groups}
398
+ rowKey="unique_key"
399
+ pagination={false}
400
+ search={false}
401
+ options={false}
402
+ size="small"
403
+ toolBarRender={false}
404
+ />
405
+ );
406
+ };
407
+
408
+ return (
409
+ <div className="queue-details-table-v2">
410
+ <ProTable
411
+ columns={columns}
412
+ dataSource={data}
413
+ rowKey="queue_name"
414
+ loading={loading}
415
+ pagination={false}
416
+ search={false}
417
+ dateFormatter="string"
418
+ headerTitle="队列概览"
419
+ actionRef={actionRef}
420
+ expandable={{
421
+ expandedRowRender,
422
+ expandedRowKeys,
423
+ onExpandedRowsChange: setExpandedRowKeys,
424
+ }}
425
+ options={{
426
+ reload: () => fetchQueueStats(),
427
+ density: true,
428
+ fullScreen: true,
429
+ setting: true,
430
+ }}
431
+ toolBarRender={() => [
432
+ expandedRowKeys.length === data.filter(d => d.consumer_groups_count > 0).length ? (
433
+ <Button
434
+ key="collapse"
435
+ icon={<CompressOutlined />}
436
+ onClick={() => setExpandedRowKeys([])}
437
+ >
438
+ 收起全部
439
+ </Button>
440
+ ) : (
441
+ <Button
442
+ key="expand"
443
+ icon={<ExpandOutlined />}
444
+ onClick={() => {
445
+ const keys = data.filter(d => d.consumer_groups_count > 0).map(d => d.queue_name);
446
+ setExpandedRowKeys(keys);
447
+ }}
448
+ >
449
+ 展开全部
450
+ </Button>
451
+ ),
452
+ // <Button
453
+ // key="refresh"
454
+ // icon={<SyncOutlined spin={loading} />}
455
+ // onClick={fetchQueueStats}
456
+ // >
457
+ // 刷新
458
+ // </Button>
459
+ ]}
460
+ />
461
+ </div>
462
+ );
463
+ });
464
+
465
+ export default QueueDetailsTableV2;