jettask 0.2.6__py3-none-any.whl → 0.2.8__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 (66) hide show
  1. jettask/core/cli.py +152 -0
  2. jettask/pg_consumer/sql/add_execution_time_field.sql +29 -0
  3. jettask/pg_consumer/sql/create_new_tables.sql +137 -0
  4. jettask/pg_consumer/sql/create_tables_v3.sql +175 -0
  5. jettask/pg_consumer/sql/migrate_to_new_structure.sql +179 -0
  6. jettask/pg_consumer/sql/modify_time_fields.sql +69 -0
  7. jettask/webui/frontend/package.json +30 -0
  8. jettask/webui/frontend/src/App.css +109 -0
  9. jettask/webui/frontend/src/App.jsx +66 -0
  10. jettask/webui/frontend/src/components/NamespaceSelector.jsx +166 -0
  11. jettask/webui/frontend/src/components/QueueBacklogChart.jsx +298 -0
  12. jettask/webui/frontend/src/components/QueueBacklogTrend.jsx +638 -0
  13. jettask/webui/frontend/src/components/QueueDetailsTable.css +65 -0
  14. jettask/webui/frontend/src/components/QueueDetailsTable.jsx +487 -0
  15. jettask/webui/frontend/src/components/QueueDetailsTableV2.jsx +465 -0
  16. jettask/webui/frontend/src/components/ScheduledTaskFilter.jsx +423 -0
  17. jettask/webui/frontend/src/components/TaskFilter.jsx +425 -0
  18. jettask/webui/frontend/src/components/TimeRangeSelector.css +21 -0
  19. jettask/webui/frontend/src/components/TimeRangeSelector.jsx +160 -0
  20. jettask/webui/frontend/src/components/charts/QueueChart.jsx +111 -0
  21. jettask/webui/frontend/src/components/charts/QueueTrendChart.jsx +115 -0
  22. jettask/webui/frontend/src/components/charts/WorkerChart.jsx +40 -0
  23. jettask/webui/frontend/src/components/common/StatsCard.jsx +18 -0
  24. jettask/webui/frontend/src/components/layout/AppLayout.css +95 -0
  25. jettask/webui/frontend/src/components/layout/AppLayout.jsx +49 -0
  26. jettask/webui/frontend/src/components/layout/Header.css +106 -0
  27. jettask/webui/frontend/src/components/layout/Header.jsx +106 -0
  28. jettask/webui/frontend/src/components/layout/SideMenu.css +137 -0
  29. jettask/webui/frontend/src/components/layout/SideMenu.jsx +209 -0
  30. jettask/webui/frontend/src/components/layout/TabsNav.css +244 -0
  31. jettask/webui/frontend/src/components/layout/TabsNav.jsx +206 -0
  32. jettask/webui/frontend/src/components/layout/UserInfo.css +197 -0
  33. jettask/webui/frontend/src/components/layout/UserInfo.jsx +197 -0
  34. jettask/webui/frontend/src/contexts/LoadingContext.jsx +27 -0
  35. jettask/webui/frontend/src/contexts/NamespaceContext.jsx +72 -0
  36. jettask/webui/frontend/src/contexts/TabsContext.backup.jsx +245 -0
  37. jettask/webui/frontend/src/index.css +114 -0
  38. jettask/webui/frontend/src/main.jsx +20 -0
  39. jettask/webui/frontend/src/pages/Alerts.jsx +684 -0
  40. jettask/webui/frontend/src/pages/Dashboard/index.css +35 -0
  41. jettask/webui/frontend/src/pages/Dashboard/index.jsx +281 -0
  42. jettask/webui/frontend/src/pages/Dashboard.jsx +1330 -0
  43. jettask/webui/frontend/src/pages/QueueDetail.jsx +1117 -0
  44. jettask/webui/frontend/src/pages/QueueMonitor.jsx +527 -0
  45. jettask/webui/frontend/src/pages/Queues.jsx +12 -0
  46. jettask/webui/frontend/src/pages/ScheduledTasks.jsx +809 -0
  47. jettask/webui/frontend/src/pages/Settings.jsx +800 -0
  48. jettask/webui/frontend/src/pages/Workers.jsx +12 -0
  49. jettask/webui/frontend/src/services/api.js +114 -0
  50. jettask/webui/frontend/src/services/queueTrend.js +152 -0
  51. jettask/webui/frontend/src/utils/suppressWarnings.js +22 -0
  52. jettask/webui/frontend/src/utils/userPreferences.js +154 -0
  53. jettask/webui/frontend/vite.config.js +26 -0
  54. {jettask-0.2.6.dist-info → jettask-0.2.8.dist-info}/METADATA +70 -2
  55. {jettask-0.2.6.dist-info → jettask-0.2.8.dist-info}/RECORD +59 -14
  56. jettask/webui/static/dist/assets/index-7129cfe1.css +0 -1
  57. jettask/webui/static/dist/assets/index-8d1935cc.js +0 -774
  58. jettask/webui/static/dist/index.html +0 -15
  59. jettask/webui/static/index.html +0 -1734
  60. jettask/webui/static/queue.html +0 -981
  61. jettask/webui/static/queues.html +0 -549
  62. jettask/webui/static/workers.html +0 -734
  63. {jettask-0.2.6.dist-info → jettask-0.2.8.dist-info}/WHEEL +0 -0
  64. {jettask-0.2.6.dist-info → jettask-0.2.8.dist-info}/entry_points.txt +0 -0
  65. {jettask-0.2.6.dist-info → jettask-0.2.8.dist-info}/licenses/LICENSE +0 -0
  66. {jettask-0.2.6.dist-info → jettask-0.2.8.dist-info}/top_level.txt +0 -0
@@ -1,549 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="zh-CN">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>队列 - Jettask Monitor</title>
7
-
8
- <!-- React -->
9
- <script crossorigin src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
10
- <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
11
-
12
- <!-- Babel for JSX -->
13
- <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
14
-
15
- <!-- Ant Design -->
16
- <link rel="stylesheet" href="https://unpkg.com/antd@4.24.13/dist/antd.min.css" />
17
- <script src="https://unpkg.com/moment@2.29.4/moment.js"></script>
18
- <script src="https://unpkg.com/antd@4.24.13/dist/antd.min.js"></script>
19
-
20
- <!-- Icons -->
21
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
22
-
23
- <style>
24
- * {
25
- margin: 0;
26
- padding: 0;
27
- box-sizing: border-box;
28
- }
29
-
30
- :root {
31
- --primary-color: #177ddc;
32
- --primary-gradient: linear-gradient(135deg, #1f1f1f 0%, #434343 100%);
33
- --success-color: #49aa19;
34
- --warning-color: #d89614;
35
- --error-color: #a61d24;
36
- --info-color: #177ddc;
37
- --bg-primary: #000000;
38
- --bg-secondary: #141414;
39
- --bg-card: #1f1f1f;
40
- --text-primary: #rgba(255, 255, 255, 0.85);
41
- --text-secondary: rgba(255, 255, 255, 0.65);
42
- --border-color: rgba(255, 255, 255, 0.12);
43
- }
44
-
45
- body {
46
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
47
- background: #000000;
48
- min-height: 100vh;
49
- position: relative;
50
- color: rgba(255, 255, 255, 0.85);
51
- }
52
-
53
- body::before {
54
- content: '';
55
- position: fixed;
56
- top: 0;
57
- left: 0;
58
- right: 0;
59
- bottom: 0;
60
- background:
61
- radial-gradient(circle at 20% 50%, rgba(23, 125, 220, 0.1) 0%, transparent 50%),
62
- radial-gradient(circle at 80% 20%, rgba(73, 170, 25, 0.1) 0%, transparent 50%),
63
- radial-gradient(circle at 40% 80%, rgba(216, 150, 20, 0.1) 0%, transparent 50%);
64
- pointer-events: none;
65
- z-index: 0;
66
- }
67
-
68
- #root {
69
- position: relative;
70
- z-index: 1;
71
- }
72
-
73
- .app-container {
74
- min-height: 100vh;
75
- backdrop-filter: blur(10px);
76
- background: rgba(0, 0, 0, 0.5);
77
- }
78
-
79
- .nav-container {
80
- background: rgba(20, 20, 20, 0.8);
81
- backdrop-filter: blur(20px);
82
- border-bottom: 1px solid rgba(255, 255, 255, 0.08);
83
- padding: 16px 0;
84
- position: sticky;
85
- top: 0;
86
- z-index: 1000;
87
- }
88
-
89
- .nav-content {
90
- max-width: 1400px;
91
- margin: 0 auto;
92
- padding: 0 30px;
93
- display: flex;
94
- align-items: center;
95
- justify-content: space-between;
96
- }
97
-
98
- .nav-title {
99
- color: white;
100
- font-size: 1.5rem;
101
- font-weight: 700;
102
- display: flex;
103
- align-items: center;
104
- gap: 12px;
105
- }
106
-
107
- .main-content {
108
- padding: 30px;
109
- max-width: 1400px;
110
- margin: 0 auto;
111
- }
112
-
113
- .gradient-text {
114
- background: linear-gradient(135deg, #177ddc 0%, #69c0ff 100%);
115
- -webkit-background-clip: text;
116
- -webkit-text-fill-color: transparent;
117
- background-clip: text;
118
- font-weight: 700;
119
- }
120
-
121
- /* Ant Design 暗色主题覆盖 */
122
- .ant-card {
123
- background: rgba(31, 31, 31, 0.95) !important;
124
- border-color: rgba(255, 255, 255, 0.08) !important;
125
- }
126
-
127
- .ant-statistic-title {
128
- color: rgba(255, 255, 255, 0.65) !important;
129
- }
130
-
131
- .ant-table {
132
- background: transparent !important;
133
- }
134
-
135
- .ant-table-thead > tr > th {
136
- background: rgba(255, 255, 255, 0.04) !important;
137
- border-bottom-color: rgba(255, 255, 255, 0.08) !important;
138
- }
139
-
140
- .ant-table-tbody > tr > td {
141
- border-bottom-color: rgba(255, 255, 255, 0.08) !important;
142
- }
143
-
144
- .ant-table-tbody > tr:hover > td {
145
- background: rgba(255, 255, 255, 0.04) !important;
146
- }
147
-
148
- /* 修复导航按钮样式 */
149
- .ant-btn-text {
150
- color: rgba(255, 255, 255, 0.65) !important;
151
- }
152
-
153
- .ant-btn-text:hover {
154
- color: rgba(255, 255, 255, 0.85) !important;
155
- background: rgba(255, 255, 255, 0.08) !important;
156
- }
157
-
158
- .ant-btn-primary {
159
- background: #177ddc !important;
160
- border-color: #177ddc !important;
161
- }
162
-
163
- .ant-btn-primary:hover {
164
- background: #1890ff !important;
165
- border-color: #1890ff !important;
166
- }
167
-
168
- /* 分页器样式 */
169
- .ant-pagination-item {
170
- background: rgba(255, 255, 255, 0.08) !important;
171
- border-color: rgba(255, 255, 255, 0.12) !important;
172
- }
173
-
174
- .ant-pagination-item a {
175
- color: rgba(255, 255, 255, 0.85) !important;
176
- }
177
-
178
- .ant-pagination-item-active {
179
- background: #177ddc !important;
180
- border-color: #177ddc !important;
181
- }
182
-
183
- /* Input输入框样式 */
184
- .ant-input {
185
- background: rgba(255, 255, 255, 0.08) !important;
186
- border-color: rgba(255, 255, 255, 0.12) !important;
187
- color: rgba(255, 255, 255, 0.85) !important;
188
- }
189
-
190
- .ant-input::placeholder {
191
- color: rgba(255, 255, 255, 0.45) !important;
192
- }
193
-
194
- .queue-card {
195
- background: rgba(31, 31, 31, 0.8) !important;
196
- backdrop-filter: blur(20px);
197
- border-radius: 20px;
198
- border: 1px solid rgba(255, 255, 255, 0.08);
199
- overflow: hidden;
200
- transition: all 0.3s ease;
201
- cursor: pointer;
202
- }
203
-
204
- .queue-card:hover {
205
- transform: translateY(-5px);
206
- box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5);
207
- background: rgba(31, 31, 31, 0.95) !important;
208
- border-color: rgba(255, 255, 255, 0.12);
209
- }
210
-
211
- /* 响应式设计 */
212
- @media (max-width: 1024px) {
213
- .main-content {
214
- padding: 16px;
215
- }
216
- }
217
-
218
- /* 自定义滚动条 */
219
- ::-webkit-scrollbar {
220
- width: 6px;
221
- }
222
-
223
- ::-webkit-scrollbar-track {
224
- background: rgba(255, 255, 255, 0.1);
225
- border-radius: 3px;
226
- }
227
-
228
- ::-webkit-scrollbar-thumb {
229
- background: rgba(255, 255, 255, 0.3);
230
- border-radius: 3px;
231
- }
232
-
233
- ::-webkit-scrollbar-thumb:hover {
234
- background: rgba(255, 255, 255, 0.5);
235
- }
236
- </style>
237
- </head>
238
- <body>
239
- <div id="root"></div>
240
-
241
- <script type="text/babel">
242
- const { useState, useEffect, useCallback } = React;
243
- const {
244
- Layout, Card, Row, Col, Table, Tag, Button, Input, Select,
245
- Statistic, Progress, Badge, Space, Typography, Tooltip, Empty,
246
- message, Spin, ConfigProvider
247
- } = antd;
248
-
249
- const { Title, Text } = Typography;
250
- const { Search } = Input;
251
-
252
- function QueuesPage() {
253
- const [loading, setLoading] = useState(false);
254
- const [searchKeyword, setSearchKeyword] = useState('');
255
- const [queues, setQueues] = useState([]);
256
- const [globalStats, setGlobalStats] = useState({
257
- total_queues: 0,
258
- messages: 0,
259
- messages_ready: 0,
260
- messages_unacknowledged: 0,
261
- consumers: 0
262
- });
263
-
264
- // 格式化时间
265
- const formatTime = useCallback((seconds) => {
266
- if (!seconds || seconds === 0) return '-';
267
- if (seconds < 1) return `${Math.round(seconds * 1000)}ms`;
268
- if (seconds < 60) return `${seconds.toFixed(2)}s`;
269
- const minutes = Math.floor(seconds / 60);
270
- const remainingSeconds = (seconds % 60).toFixed(1);
271
- return `${minutes}m${remainingSeconds}s`;
272
- }, []);
273
-
274
- // 过滤队列
275
- const filteredQueues = React.useMemo(() => {
276
- if (!searchKeyword) return queues;
277
- return queues.filter(queue =>
278
- queue.name.toLowerCase().includes(searchKeyword.toLowerCase())
279
- );
280
- }, [queues, searchKeyword]);
281
-
282
- // 表格列定义
283
- const columns = [
284
- {
285
- title: '队列名称',
286
- dataIndex: 'name',
287
- key: 'name',
288
- render: (name) => (
289
- <Space>
290
- <Tag color="blue">Q</Tag>
291
- <Text strong style={{color: '#1890ff', cursor: 'pointer'}}
292
- onClick={() => window.location.href = `/queue.html?name=${encodeURIComponent(name)}`}>
293
- {name}
294
- </Text>
295
- </Space>
296
- ),
297
- },
298
- {
299
- title: '消息总数',
300
- dataIndex: 'messages',
301
- key: 'messages',
302
- align: 'center',
303
- render: (value) => <Tag color="blue">{value || 0}</Tag>,
304
- },
305
- {
306
- title: '就绪消息',
307
- dataIndex: 'messages_ready',
308
- key: 'messages_ready',
309
- align: 'center',
310
- render: (value) => <Tag color="cyan">{value || 0}</Tag>,
311
- },
312
- {
313
- title: '未确认消息',
314
- dataIndex: 'messages_unacknowledged',
315
- key: 'messages_unacknowledged',
316
- align: 'center',
317
- render: (value) => <Tag color="orange">{value || 0}</Tag>,
318
- },
319
- {
320
- title: '消费者',
321
- dataIndex: 'consumers',
322
- key: 'consumers',
323
- align: 'center',
324
- render: (value) => <Tag color="green">{value || 0}</Tag>,
325
- },
326
- {
327
- title: '任务成功/失败',
328
- key: 'task_stats',
329
- align: 'center',
330
- render: (_, record) => (
331
- <Space>
332
- <Text type="success">{record.success_count || 0}</Text>
333
- <span>/</span>
334
- <Text type="danger">{record.failed_count || 0}</Text>
335
- </Space>
336
- ),
337
- },
338
- {
339
- title: '平均处理时间',
340
- dataIndex: 'avg_processing_time',
341
- key: 'avg_processing_time',
342
- align: 'center',
343
- render: (time) => formatTime(time),
344
- },
345
- {
346
- title: '操作',
347
- key: 'action',
348
- align: 'center',
349
- render: (_, record) => (
350
- <Button
351
- type="primary"
352
- size="small"
353
- onClick={() => window.location.href = `/queue.html?name=${encodeURIComponent(record.name)}`}
354
- >
355
- 查看详情
356
- </Button>
357
- ),
358
- },
359
- ];
360
-
361
- // 加载数据
362
- const loadData = async () => {
363
- try {
364
- setLoading(true);
365
-
366
- const [queuesResp, statsResp] = await Promise.all([
367
- fetch('/api/queues'),
368
- fetch('/api/stats')
369
- ]);
370
-
371
- if (queuesResp.ok) {
372
- const data = await queuesResp.json();
373
- setQueues(data.queues || []);
374
- }
375
-
376
- if (statsResp.ok) {
377
- const data = await statsResp.json();
378
- setGlobalStats({
379
- total_queues: data.total_queues || 0,
380
- messages: data.messages || 0,
381
- messages_ready: data.messages_ready || 0,
382
- messages_unacknowledged: data.messages_unacknowledged || 0,
383
- consumers: data.consumers || 0
384
- });
385
- }
386
- } catch (error) {
387
- console.error('Failed to load data:', error);
388
- message.error('加载数据失败');
389
- } finally {
390
- setLoading(false);
391
- }
392
- };
393
-
394
- // 初始化
395
- useEffect(() => {
396
- loadData();
397
-
398
- // 定时刷新
399
- const interval = setInterval(loadData, 5000);
400
-
401
- return () => {
402
- clearInterval(interval);
403
- };
404
- }, []);
405
-
406
-
407
- return (
408
- <div className="app-container">
409
- {/* 导航栏 */}
410
- <div className="nav-container">
411
- <div className="nav-content">
412
- <div className="nav-title">
413
- <i className="fas fa-rocket" style={{fontSize: '1.5rem'}} />
414
- Jettask Monitor
415
- </div>
416
- <Space size="large">
417
- <Button type="text" size="large" onClick={() => window.location.href = '/'}>
418
- 概览
419
- </Button>
420
- <Button type="primary" size="large">
421
- 队列
422
- </Button>
423
- <Button type="text" size="large" onClick={() => window.location.href = '/workers.html'}>
424
- Workers
425
- </Button>
426
- </Space>
427
- </div>
428
- </div>
429
-
430
- {/* 主要内容 */}
431
- <div className="main-content">
432
- {/* 统计概览 */}
433
- <Row gutter={[24, 24]} style={{marginBottom: 30}}>
434
- <Col xs={24} sm={12} md={6}>
435
- <Card className="queue-card">
436
- <Statistic
437
- title="总队列数"
438
- value={globalStats.total_queues}
439
- valueStyle={{color: '#177ddc'}}
440
- prefix={<i className="fas fa-layer-group" />}
441
- />
442
- </Card>
443
- </Col>
444
- <Col xs={24} sm={12} md={6}>
445
- <Card className="queue-card">
446
- <Statistic
447
- title="消息总数"
448
- value={globalStats.messages}
449
- valueStyle={{color: '#722ed1'}}
450
- prefix={<i className="fas fa-envelope" />}
451
- />
452
- </Card>
453
- </Col>
454
- <Col xs={24} sm={12} md={6}>
455
- <Card className="queue-card">
456
- <Statistic
457
- title="就绪消息"
458
- value={globalStats.messages_ready}
459
- valueStyle={{color: '#13c2c2'}}
460
- prefix={<i className="fas fa-inbox" />}
461
- />
462
- </Card>
463
- </Col>
464
- <Col xs={24} sm={12} md={6}>
465
- <Card className="queue-card">
466
- <Statistic
467
- title="未确认消息"
468
- value={globalStats.messages_unacknowledged}
469
- valueStyle={{color: '#faad14'}}
470
- prefix={<i className="fas fa-hourglass-half" />}
471
- />
472
- </Card>
473
- </Col>
474
- </Row>
475
-
476
- {/* 队列列表 */}
477
- <Card
478
- title={
479
- <Title level={4} className="gradient-text">
480
- <i className="fas fa-list" style={{marginRight: 12}} />
481
- 队列列表
482
- </Title>
483
- }
484
- extra={
485
- <Space>
486
- <Search
487
- placeholder="搜索队列..."
488
- allowClear
489
- value={searchKeyword}
490
- onChange={e => setSearchKeyword(e.target.value)}
491
- style={{width: 200}}
492
- />
493
- <Button
494
- type="primary"
495
- icon={<i className="fas fa-sync-alt" />}
496
- onClick={loadData}
497
- loading={loading}
498
- >
499
- 刷新
500
- </Button>
501
- </Space>
502
- }
503
- style={{
504
- background: 'rgba(31, 31, 31, 0.8)',
505
- backdropFilter: 'blur(20px)',
506
- borderRadius: '20px',
507
- border: '1px solid rgba(255, 255, 255, 0.08)'
508
- }}
509
- >
510
- {loading ? (
511
- <div style={{textAlign: 'center', padding: '50px'}}>
512
- <Spin size="large" />
513
- </div>
514
- ) : filteredQueues.length === 0 ? (
515
- <Empty description="暂无队列" />
516
- ) : (
517
- <Table
518
- columns={columns}
519
- dataSource={filteredQueues}
520
- rowKey="name"
521
- pagination={{
522
- defaultPageSize: 20,
523
- showSizeChanger: true,
524
- showQuickJumper: true,
525
- showTotal: (total) => `共 ${total} 个队列`
526
- }}
527
- size="middle"
528
- />
529
- )}
530
- </Card>
531
- </div>
532
- </div>
533
- );
534
- }
535
-
536
- // 主应用组件
537
- function App() {
538
- return (
539
- <ConfigProvider>
540
- <QueuesPage />
541
- </ConfigProvider>
542
- );
543
- }
544
-
545
- // 渲染应用
546
- ReactDOM.render(React.createElement(App), document.getElementById('root'));
547
- </script>
548
- </body>
549
- </html>