jettask 0.2.4__py3-none-any.whl → 0.2.6__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 (94) hide show
  1. jettask/core/cli.py +20 -24
  2. jettask/monitor/run_backlog_collector.py +96 -0
  3. jettask/monitor/stream_backlog_monitor.py +362 -0
  4. jettask/pg_consumer/pg_consumer_v2.py +403 -0
  5. jettask/pg_consumer/sql_utils.py +182 -0
  6. jettask/scheduler/__init__.py +17 -0
  7. jettask/scheduler/add_execution_count.sql +11 -0
  8. jettask/scheduler/add_priority_field.sql +26 -0
  9. jettask/scheduler/add_scheduler_id.sql +25 -0
  10. jettask/scheduler/add_scheduler_id_index.sql +10 -0
  11. jettask/scheduler/loader.py +249 -0
  12. jettask/scheduler/make_scheduler_id_required.sql +28 -0
  13. jettask/scheduler/manager.py +696 -0
  14. jettask/scheduler/migrate_interval_seconds.sql +9 -0
  15. jettask/scheduler/models.py +200 -0
  16. jettask/scheduler/multi_namespace_scheduler.py +294 -0
  17. jettask/scheduler/performance_optimization.sql +45 -0
  18. jettask/scheduler/run_scheduler.py +186 -0
  19. jettask/scheduler/scheduler.py +715 -0
  20. jettask/scheduler/schema.sql +84 -0
  21. jettask/scheduler/unified_manager.py +450 -0
  22. jettask/scheduler/unified_scheduler_manager.py +280 -0
  23. jettask/webui/backend/api/__init__.py +3 -0
  24. jettask/webui/backend/api/v1/__init__.py +17 -0
  25. jettask/webui/backend/api/v1/monitoring.py +431 -0
  26. jettask/webui/backend/api/v1/namespaces.py +504 -0
  27. jettask/webui/backend/api/v1/queues.py +342 -0
  28. jettask/webui/backend/api/v1/tasks.py +367 -0
  29. jettask/webui/backend/core/__init__.py +3 -0
  30. jettask/webui/backend/core/cache.py +221 -0
  31. jettask/webui/backend/core/database.py +200 -0
  32. jettask/webui/backend/core/exceptions.py +102 -0
  33. jettask/webui/backend/models/__init__.py +3 -0
  34. jettask/webui/backend/models/requests.py +236 -0
  35. jettask/webui/backend/models/responses.py +230 -0
  36. jettask/webui/backend/services/__init__.py +3 -0
  37. jettask/webui/frontend/index.html +13 -0
  38. jettask/webui/models/__init__.py +3 -0
  39. jettask/webui/models/namespace.py +63 -0
  40. jettask/webui/sql/batch_upsert_functions.sql +178 -0
  41. jettask/webui/sql/init_database.sql +640 -0
  42. {jettask-0.2.4.dist-info → jettask-0.2.6.dist-info}/METADATA +11 -9
  43. {jettask-0.2.4.dist-info → jettask-0.2.6.dist-info}/RECORD +47 -54
  44. jettask/webui/frontend/package-lock.json +0 -4833
  45. jettask/webui/frontend/package.json +0 -30
  46. jettask/webui/frontend/src/App.css +0 -109
  47. jettask/webui/frontend/src/App.jsx +0 -66
  48. jettask/webui/frontend/src/components/NamespaceSelector.jsx +0 -166
  49. jettask/webui/frontend/src/components/QueueBacklogChart.jsx +0 -298
  50. jettask/webui/frontend/src/components/QueueBacklogTrend.jsx +0 -638
  51. jettask/webui/frontend/src/components/QueueDetailsTable.css +0 -65
  52. jettask/webui/frontend/src/components/QueueDetailsTable.jsx +0 -487
  53. jettask/webui/frontend/src/components/QueueDetailsTableV2.jsx +0 -465
  54. jettask/webui/frontend/src/components/ScheduledTaskFilter.jsx +0 -423
  55. jettask/webui/frontend/src/components/TaskFilter.jsx +0 -425
  56. jettask/webui/frontend/src/components/TimeRangeSelector.css +0 -21
  57. jettask/webui/frontend/src/components/TimeRangeSelector.jsx +0 -160
  58. jettask/webui/frontend/src/components/charts/QueueChart.jsx +0 -111
  59. jettask/webui/frontend/src/components/charts/QueueTrendChart.jsx +0 -115
  60. jettask/webui/frontend/src/components/charts/WorkerChart.jsx +0 -40
  61. jettask/webui/frontend/src/components/common/StatsCard.jsx +0 -18
  62. jettask/webui/frontend/src/components/layout/AppLayout.css +0 -95
  63. jettask/webui/frontend/src/components/layout/AppLayout.jsx +0 -49
  64. jettask/webui/frontend/src/components/layout/Header.css +0 -106
  65. jettask/webui/frontend/src/components/layout/Header.jsx +0 -106
  66. jettask/webui/frontend/src/components/layout/SideMenu.css +0 -137
  67. jettask/webui/frontend/src/components/layout/SideMenu.jsx +0 -209
  68. jettask/webui/frontend/src/components/layout/TabsNav.css +0 -244
  69. jettask/webui/frontend/src/components/layout/TabsNav.jsx +0 -206
  70. jettask/webui/frontend/src/components/layout/UserInfo.css +0 -197
  71. jettask/webui/frontend/src/components/layout/UserInfo.jsx +0 -197
  72. jettask/webui/frontend/src/contexts/LoadingContext.jsx +0 -27
  73. jettask/webui/frontend/src/contexts/NamespaceContext.jsx +0 -72
  74. jettask/webui/frontend/src/contexts/TabsContext.backup.jsx +0 -245
  75. jettask/webui/frontend/src/index.css +0 -114
  76. jettask/webui/frontend/src/main.jsx +0 -20
  77. jettask/webui/frontend/src/pages/Alerts.jsx +0 -684
  78. jettask/webui/frontend/src/pages/Dashboard/index.css +0 -35
  79. jettask/webui/frontend/src/pages/Dashboard/index.jsx +0 -281
  80. jettask/webui/frontend/src/pages/Dashboard.jsx +0 -1330
  81. jettask/webui/frontend/src/pages/QueueDetail.jsx +0 -1117
  82. jettask/webui/frontend/src/pages/QueueMonitor.jsx +0 -527
  83. jettask/webui/frontend/src/pages/Queues.jsx +0 -12
  84. jettask/webui/frontend/src/pages/ScheduledTasks.jsx +0 -809
  85. jettask/webui/frontend/src/pages/Settings.jsx +0 -800
  86. jettask/webui/frontend/src/pages/Workers.jsx +0 -12
  87. jettask/webui/frontend/src/services/api.js +0 -114
  88. jettask/webui/frontend/src/services/queueTrend.js +0 -152
  89. jettask/webui/frontend/src/utils/suppressWarnings.js +0 -22
  90. jettask/webui/frontend/src/utils/userPreferences.js +0 -154
  91. {jettask-0.2.4.dist-info → jettask-0.2.6.dist-info}/WHEEL +0 -0
  92. {jettask-0.2.4.dist-info → jettask-0.2.6.dist-info}/entry_points.txt +0 -0
  93. {jettask-0.2.4.dist-info → jettask-0.2.6.dist-info}/licenses/LICENSE +0 -0
  94. {jettask-0.2.4.dist-info → jettask-0.2.6.dist-info}/top_level.txt +0 -0
@@ -1,465 +0,0 @@
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;