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,423 +0,0 @@
1
- import React, { useState } from 'react';
2
- import { Button, Popover, Form, Select, Input, Space, Tag, message } from 'antd';
3
- import { PlusOutlined, DeleteOutlined } from '@ant-design/icons';
4
-
5
- const { Option } = Select;
6
-
7
- // 定时任务可用的字段列表
8
- const AVAILABLE_FIELDS = [
9
- { value: 'id', label: '任务ID', type: 'number' },
10
- { value: 'scheduler_id', label: '调度器ID', type: 'string' },
11
- { value: 'name', label: '任务名称', type: 'string' },
12
- { value: 'queue_name', label: '队列名称', type: 'string' },
13
- { value: 'schedule_type', label: '调度类型', type: 'enum', options: ['cron', 'interval', 'once'] },
14
- { value: 'is_active', label: '状态', type: 'boolean' },
15
- { value: 'description', label: '描述', type: 'string' },
16
- { value: 'last_run', label: '上次执行时间', type: 'datetime' },
17
- { value: 'next_run', label: '下次执行时间', type: 'datetime' },
18
- { value: 'created_at', label: '创建时间', type: 'datetime' },
19
- { value: 'task_data', label: '任务参数', type: 'json' },
20
- { value: 'tags', label: '标签', type: 'json' },
21
- { value: 'metadata', label: '元数据', type: 'json' },
22
- ];
23
-
24
- // 操作符列表
25
- const OPERATORS = {
26
- string: [
27
- { value: 'eq', label: '等于' },
28
- { value: 'ne', label: '不等于' },
29
- { value: 'contains', label: '包含' },
30
- { value: 'starts_with', label: '开始于' },
31
- { value: 'ends_with', label: '结束于' },
32
- { value: 'is_null', label: '为空' },
33
- { value: 'is_not_null', label: '不为空' },
34
- ],
35
- number: [
36
- { value: 'eq', label: '等于' },
37
- { value: 'ne', label: '不等于' },
38
- { value: 'gt', label: '大于' },
39
- { value: 'lt', label: '小于' },
40
- { value: 'gte', label: '大于等于' },
41
- { value: 'lte', label: '小于等于' },
42
- { value: 'is_null', label: '为空' },
43
- { value: 'is_not_null', label: '不为空' },
44
- ],
45
- datetime: [
46
- { value: 'gt', label: '晚于' },
47
- { value: 'lt', label: '早于' },
48
- { value: 'gte', label: '不早于' },
49
- { value: 'lte', label: '不晚于' },
50
- { value: 'is_null', label: '为空' },
51
- { value: 'is_not_null', label: '不为空' },
52
- ],
53
- enum: [
54
- { value: 'eq', label: '等于' },
55
- { value: 'ne', label: '不等于' },
56
- { value: 'in', label: '在列表中' },
57
- { value: 'not_in', label: '不在列表中' },
58
- ],
59
- boolean: [
60
- { value: 'eq', label: '等于' },
61
- ],
62
- json: [
63
- { value: 'contains', label: '包含文本' },
64
- { value: 'json_key_exists', label: '包含键名' },
65
- { value: 'json_path_value', label: 'JSON路径值' },
66
- { value: 'is_null', label: '为空' },
67
- { value: 'is_not_null', label: '不为空' },
68
- ],
69
- };
70
-
71
- function ScheduledTaskFilter({ filters, onFiltersChange }) {
72
- const [visible, setVisible] = useState(false);
73
- const [form] = Form.useForm();
74
- const [selectedField, setSelectedField] = useState(null);
75
- const [selectedOperator, setSelectedOperator] = useState(null);
76
- const [disabledFilters, setDisabledFilters] = useState(new Set()); // 记录被禁用的筛选条件索引
77
-
78
- // 获取当前字段的类型
79
- const getFieldType = (fieldValue) => {
80
- const field = AVAILABLE_FIELDS.find(f => f.value === fieldValue);
81
- return field ? field.type : 'string';
82
- };
83
-
84
- // 获取当前字段的选项(用于枚举类型)
85
- const getFieldOptions = (fieldValue) => {
86
- const field = AVAILABLE_FIELDS.find(f => f.value === fieldValue);
87
- return field && field.options ? field.options : [];
88
- };
89
-
90
- // 添加筛选条件
91
- const handleAddFilter = () => {
92
- form.validateFields().then(values => {
93
- const newFilter = {
94
- id: Date.now(), // 添加唯一ID
95
- field: values.field,
96
- operator: values.operator,
97
- value: values.value,
98
- enabled: true, // 默认启用
99
- };
100
-
101
- // 处理特殊操作符
102
- if (values.operator === 'is_null' || values.operator === 'is_not_null') {
103
- newFilter.value = null;
104
- } else if (values.operator === 'in' || values.operator === 'not_in') {
105
- // 如果是枚举类型的多选
106
- if (getFieldType(values.field) === 'enum') {
107
- newFilter.value = values.value; // 已经是数组
108
- } else {
109
- // 普通文本,用逗号分隔
110
- newFilter.value = values.value.split(',').map(v => v.trim());
111
- }
112
- } else if (getFieldType(values.field) === 'boolean') {
113
- // 布尔类型转换
114
- newFilter.value = values.value === 'true';
115
- }
116
-
117
- const updatedFilters = [...(filters || []), newFilter];
118
- onFiltersChange(updatedFilters);
119
-
120
- // 重置表单
121
- form.resetFields();
122
- setSelectedField(null);
123
- setSelectedOperator(null);
124
- setVisible(false);
125
- message.success('筛选条件已添加');
126
- });
127
- };
128
-
129
- // 切换筛选条件的启用/禁用状态
130
- const handleToggleFilter = (index) => {
131
- const updatedFilters = [...filters];
132
- updatedFilters[index] = {
133
- ...updatedFilters[index],
134
- enabled: !updatedFilters[index].enabled
135
- };
136
- onFiltersChange(updatedFilters);
137
- };
138
-
139
- // 删除筛选条件
140
- const handleRemoveFilter = (index) => {
141
- const updatedFilters = filters.filter((_, i) => i !== index);
142
- onFiltersChange(updatedFilters);
143
- message.success('筛选条件已删除');
144
- };
145
-
146
- // 渲染筛选条件标签
147
- const renderFilterTag = (filter, index) => {
148
- const field = AVAILABLE_FIELDS.find(f => f.value === filter.field);
149
- const fieldLabel = field ? field.label : filter.field;
150
-
151
- const operator = OPERATORS[getFieldType(filter.field)]?.find(op => op.value === filter.operator);
152
- const operatorLabel = operator ? operator.label : filter.operator;
153
-
154
- let valueLabel = filter.value;
155
- if (filter.operator === 'is_null' || filter.operator === 'is_not_null') {
156
- valueLabel = '';
157
- } else if (Array.isArray(filter.value)) {
158
- valueLabel = filter.value.join(', ');
159
- } else if (typeof filter.value === 'boolean') {
160
- valueLabel = filter.value ? '是' : '否';
161
- }
162
-
163
- const tagLabel = `${fieldLabel} ${operatorLabel} ${valueLabel}`.trim();
164
- const isDisabled = filter.enabled === false;
165
-
166
- return (
167
- <Tag
168
- key={index}
169
- closable
170
- onClose={() => handleRemoveFilter(index)}
171
- onClick={() => handleToggleFilter(index)}
172
- style={{
173
- margin: 0,
174
- height: '24px',
175
- lineHeight: '22px',
176
- display: 'inline-flex',
177
- alignItems: 'center',
178
- cursor: 'pointer',
179
- opacity: isDisabled ? 0.5 : 1,
180
- textDecoration: isDisabled ? 'line-through' : 'none',
181
- backgroundColor: isDisabled ? '#f5f5f5' : undefined,
182
- borderStyle: isDisabled ? 'dashed' : 'solid',
183
- }}
184
- color={isDisabled ? 'default' : undefined}
185
- >
186
- {tagLabel}
187
- </Tag>
188
- );
189
- };
190
-
191
- // 渲染值输入组件
192
- const renderValueInput = () => {
193
- if (!selectedField) return null;
194
-
195
- const fieldType = getFieldType(selectedField);
196
- const needsValue = selectedOperator !== 'is_null' && selectedOperator !== 'is_not_null';
197
-
198
- if (!needsValue) return null;
199
-
200
- if (fieldType === 'enum') {
201
- const options = getFieldOptions(selectedField);
202
- if (selectedOperator === 'in' || selectedOperator === 'not_in') {
203
- return (
204
- <Form.Item
205
- name="value"
206
- label="值"
207
- rules={[{ required: true, message: '请选择值' }]}
208
- >
209
- <Select mode="multiple" placeholder="选择多个值">
210
- {options.map(opt => (
211
- <Option key={opt} value={opt}>{opt}</Option>
212
- ))}
213
- </Select>
214
- </Form.Item>
215
- );
216
- } else {
217
- return (
218
- <Form.Item
219
- name="value"
220
- label="值"
221
- rules={[{ required: true, message: '请选择值' }]}
222
- >
223
- <Select placeholder="选择值">
224
- {options.map(opt => (
225
- <Option key={opt} value={opt}>{opt}</Option>
226
- ))}
227
- </Select>
228
- </Form.Item>
229
- );
230
- }
231
- } else if (fieldType === 'boolean') {
232
- return (
233
- <Form.Item
234
- name="value"
235
- label="值"
236
- rules={[{ required: true, message: '请选择值' }]}
237
- >
238
- <Select placeholder="选择值">
239
- <Option value="true">是</Option>
240
- <Option value="false">否</Option>
241
- </Select>
242
- </Form.Item>
243
- );
244
- } else if (fieldType === 'number') {
245
- return (
246
- <Form.Item
247
- name="value"
248
- label="值"
249
- rules={[{ required: true, message: '请输入值' }]}
250
- >
251
- <Input type="number" placeholder="输入数值" />
252
- </Form.Item>
253
- );
254
- } else if (fieldType === 'datetime') {
255
- return (
256
- <Form.Item
257
- name="value"
258
- label="值"
259
- rules={[{ required: true, message: '请输入值' }]}
260
- >
261
- <Input
262
- placeholder="格式: YYYY-MM-DD HH:mm:ss"
263
- title="请输入日期时间,格式: YYYY-MM-DD HH:mm:ss"
264
- />
265
- </Form.Item>
266
- );
267
- } else if (fieldType === 'json') {
268
- // JSON类型的输入
269
- let label = '搜索内容';
270
- let placeholder = '输入搜索内容';
271
- let extra = '';
272
-
273
- if (selectedOperator === 'json_key_exists') {
274
- label = '键名';
275
- placeholder = '输入要查找的键名';
276
- extra = '检查JSON中是否存在指定的键';
277
- } else if (selectedOperator === 'json_path_value') {
278
- label = 'JSON路径和值';
279
- placeholder = '路径=值,如:$.user_id=123 或 $.kwargs.message=test';
280
- extra = '使用JSON路径语法,格式:路径=值。支持嵌套路径如 $.kwargs.user_id';
281
- } else if (selectedOperator === 'contains') {
282
- label = '搜索文本';
283
- placeholder = '输入要搜索的文本内容';
284
- extra = '在JSON数据中搜索包含此文本的任务';
285
- }
286
-
287
- return (
288
- <Form.Item
289
- name="value"
290
- label={label}
291
- rules={[{ required: true, message: `请输入${label}` }]}
292
- extra={extra}
293
- >
294
- <Input.TextArea
295
- placeholder={placeholder}
296
- rows={2}
297
- spellCheck={false}
298
- />
299
- </Form.Item>
300
- );
301
- } else {
302
- return (
303
- <Form.Item
304
- name="value"
305
- label="值"
306
- rules={[{ required: true, message: '请输入值' }]}
307
- >
308
- <Input placeholder={selectedOperator === 'in' || selectedOperator === 'not_in' ? '多个值用逗号分隔' : '输入值'} />
309
- </Form.Item>
310
- );
311
- }
312
- };
313
-
314
- const filterContent = (
315
- <div style={{ width: 350 }}>
316
- <Form
317
- form={form}
318
- layout="vertical"
319
- onFinish={handleAddFilter}
320
- >
321
- <Form.Item
322
- name="field"
323
- label="字段"
324
- rules={[{ required: true, message: '请选择字段' }]}
325
- >
326
- <Select
327
- placeholder="选择字段"
328
- onChange={(value) => {
329
- setSelectedField(value);
330
- setSelectedOperator(null);
331
- form.setFieldsValue({ operator: undefined, value: undefined });
332
- }}
333
- >
334
- {AVAILABLE_FIELDS.map(field => (
335
- <Option key={field.value} value={field.value}>
336
- {field.label}
337
- </Option>
338
- ))}
339
- </Select>
340
- </Form.Item>
341
-
342
- {selectedField && (
343
- <Form.Item
344
- name="operator"
345
- label="操作符"
346
- rules={[{ required: true, message: '请选择操作符' }]}
347
- >
348
- <Select
349
- placeholder="选择操作符"
350
- onChange={(value) => {
351
- setSelectedOperator(value);
352
- form.setFieldsValue({ value: undefined });
353
- }}
354
- >
355
- {OPERATORS[getFieldType(selectedField)]?.map(op => (
356
- <Option key={op.value} value={op.value}>
357
- {op.label}
358
- </Option>
359
- ))}
360
- </Select>
361
- </Form.Item>
362
- )}
363
-
364
- {renderValueInput()}
365
-
366
- <Form.Item>
367
- <Space>
368
- <Button type="primary" htmlType="submit">
369
- 添加筛选条件
370
- </Button>
371
- <Button onClick={() => {
372
- setVisible(false);
373
- form.resetFields();
374
- setSelectedField(null);
375
- setSelectedOperator(null);
376
- }}>
377
- 取消
378
- </Button>
379
- </Space>
380
- </Form.Item>
381
- </Form>
382
- </div>
383
- );
384
-
385
- return (
386
- <div style={{
387
- display: 'inline-flex',
388
- alignItems: 'center',
389
- flexWrap: 'wrap',
390
- gap: '8px',
391
- verticalAlign: 'middle'
392
- }}>
393
- {filters && filters.map((filter, index) => renderFilterTag(filter, index))}
394
-
395
- <Popover
396
- content={filterContent}
397
- title="添加筛选条件"
398
- trigger="click"
399
- open={visible}
400
- onOpenChange={setVisible}
401
- placement="bottomLeft"
402
- >
403
- <Button
404
- type="dashed"
405
- icon={<PlusOutlined />}
406
- size="small"
407
- style={{
408
- height: '24px',
409
- padding: '0 8px',
410
- display: 'inline-flex',
411
- alignItems: 'center',
412
- lineHeight: '22px',
413
- verticalAlign: 'middle'
414
- }}
415
- >
416
- Add Filter
417
- </Button>
418
- </Popover>
419
- </div>
420
- );
421
- }
422
-
423
- export default ScheduledTaskFilter;