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,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;