jettask 0.2.7__py3-none-any.whl → 0.2.9__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 +242 -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.7.dist-info → jettask-0.2.9.dist-info}/METADATA +1 -1
  55. {jettask-0.2.7.dist-info → jettask-0.2.9.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.7.dist-info → jettask-0.2.9.dist-info}/WHEEL +0 -0
  64. {jettask-0.2.7.dist-info → jettask-0.2.9.dist-info}/entry_points.txt +0 -0
  65. {jettask-0.2.7.dist-info → jettask-0.2.9.dist-info}/licenses/LICENSE +0 -0
  66. {jettask-0.2.7.dist-info → jettask-0.2.9.dist-info}/top_level.txt +0 -0
@@ -1,734 +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>Workers - 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
- <!-- Chart.js -->
21
- <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
22
-
23
- <!-- Icons -->
24
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
25
-
26
- <style>
27
- * {
28
- margin: 0;
29
- padding: 0;
30
- box-sizing: border-box;
31
- }
32
-
33
- :root {
34
- --primary-color: #177ddc;
35
- --primary-gradient: linear-gradient(135deg, #1f1f1f 0%, #434343 100%);
36
- --success-color: #49aa19;
37
- --warning-color: #d89614;
38
- --error-color: #a61d24;
39
- --info-color: #177ddc;
40
- --bg-primary: #000000;
41
- --bg-secondary: #141414;
42
- --bg-card: #1f1f1f;
43
- --text-primary: #rgba(255, 255, 255, 0.85);
44
- --text-secondary: rgba(255, 255, 255, 0.65);
45
- --border-color: rgba(255, 255, 255, 0.12);
46
- }
47
-
48
- body {
49
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
50
- background: #000000;
51
- min-height: 100vh;
52
- position: relative;
53
- color: rgba(255, 255, 255, 0.85);
54
- }
55
-
56
- body::before {
57
- content: '';
58
- position: fixed;
59
- top: 0;
60
- left: 0;
61
- right: 0;
62
- bottom: 0;
63
- background:
64
- radial-gradient(circle at 20% 50%, rgba(23, 125, 220, 0.1) 0%, transparent 50%),
65
- radial-gradient(circle at 80% 20%, rgba(73, 170, 25, 0.1) 0%, transparent 50%),
66
- radial-gradient(circle at 40% 80%, rgba(216, 150, 20, 0.1) 0%, transparent 50%);
67
- pointer-events: none;
68
- z-index: 0;
69
- }
70
-
71
- #root {
72
- position: relative;
73
- z-index: 1;
74
- }
75
-
76
- .app-container {
77
- min-height: 100vh;
78
- backdrop-filter: blur(10px);
79
- background: rgba(0, 0, 0, 0.5);
80
- }
81
-
82
- .nav-container {
83
- background: rgba(20, 20, 20, 0.8);
84
- backdrop-filter: blur(20px);
85
- border-bottom: 1px solid rgba(255, 255, 255, 0.08);
86
- padding: 16px 0;
87
- position: sticky;
88
- top: 0;
89
- z-index: 1000;
90
- }
91
-
92
- .nav-content {
93
- max-width: 1400px;
94
- margin: 0 auto;
95
- padding: 0 30px;
96
- display: flex;
97
- align-items: center;
98
- justify-content: space-between;
99
- }
100
-
101
- .nav-title {
102
- color: white;
103
- font-size: 1.5rem;
104
- font-weight: 700;
105
- display: flex;
106
- align-items: center;
107
- gap: 12px;
108
- }
109
-
110
- .main-content {
111
- padding: 30px;
112
- max-width: 1400px;
113
- margin: 0 auto;
114
- }
115
-
116
- .gradient-text {
117
- background: linear-gradient(135deg, #177ddc 0%, #69c0ff 100%);
118
- -webkit-background-clip: text;
119
- -webkit-text-fill-color: transparent;
120
- background-clip: text;
121
- font-weight: 700;
122
- }
123
-
124
- /* Ant Design 暗色主题覆盖 */
125
- .ant-card {
126
- background: rgba(31, 31, 31, 0.95) !important;
127
- border-color: rgba(255, 255, 255, 0.08) !important;
128
- }
129
-
130
- .ant-statistic-title {
131
- color: rgba(255, 255, 255, 0.65) !important;
132
- }
133
-
134
- .ant-table {
135
- background: transparent !important;
136
- }
137
-
138
- .ant-table-thead > tr > th {
139
- background: rgba(255, 255, 255, 0.04) !important;
140
- border-bottom-color: rgba(255, 255, 255, 0.08) !important;
141
- }
142
-
143
- .ant-table-tbody > tr > td {
144
- border-bottom-color: rgba(255, 255, 255, 0.08) !important;
145
- }
146
-
147
- .ant-table-tbody > tr:hover > td {
148
- background: rgba(255, 255, 255, 0.04) !important;
149
- }
150
-
151
- /* 修复导航按钮样式 */
152
- .ant-btn-text {
153
- color: rgba(255, 255, 255, 0.65) !important;
154
- }
155
-
156
- .ant-btn-text:hover {
157
- color: rgba(255, 255, 255, 0.85) !important;
158
- background: rgba(255, 255, 255, 0.08) !important;
159
- }
160
-
161
- .ant-btn-primary {
162
- background: #177ddc !important;
163
- border-color: #177ddc !important;
164
- }
165
-
166
- .ant-btn-primary:hover {
167
- background: #1890ff !important;
168
- border-color: #1890ff !important;
169
- }
170
-
171
- /* 分页器样式 */
172
- .ant-pagination-item {
173
- background: rgba(255, 255, 255, 0.08) !important;
174
- border-color: rgba(255, 255, 255, 0.12) !important;
175
- }
176
-
177
- .ant-pagination-item a {
178
- color: rgba(255, 255, 255, 0.85) !important;
179
- }
180
-
181
- .ant-pagination-item-active {
182
- background: #177ddc !important;
183
- border-color: #177ddc !important;
184
- }
185
-
186
- /* Input输入框样式 */
187
- .ant-input {
188
- background: rgba(255, 255, 255, 0.08) !important;
189
- border-color: rgba(255, 255, 255, 0.12) !important;
190
- color: rgba(255, 255, 255, 0.85) !important;
191
- }
192
-
193
- .ant-input::placeholder {
194
- color: rgba(255, 255, 255, 0.45) !important;
195
- }
196
-
197
- /* Select下拉框样式 */
198
- .ant-select-selector {
199
- background: rgba(255, 255, 255, 0.08) !important;
200
- border-color: rgba(255, 255, 255, 0.12) !important;
201
- color: rgba(255, 255, 255, 0.85) !important;
202
- }
203
-
204
- /* Switch开关样式 */
205
- .ant-switch {
206
- background: rgba(255, 255, 255, 0.25) !important;
207
- }
208
-
209
- .ant-switch-checked {
210
- background: #177ddc !important;
211
- }
212
-
213
- .worker-card {
214
- background: rgba(31, 31, 31, 0.8) !important;
215
- backdrop-filter: blur(20px);
216
- border-radius: 20px;
217
- border: 1px solid rgba(255, 255, 255, 0.08);
218
- overflow: hidden;
219
- transition: all 0.3s ease;
220
- }
221
-
222
- .worker-card:hover {
223
- box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5);
224
- background: rgba(31, 31, 31, 0.95) !important;
225
- border-color: rgba(255, 255, 255, 0.12);
226
- }
227
-
228
- /* 响应式设计 */
229
- @media (max-width: 1024px) {
230
- .main-content {
231
- padding: 16px;
232
- }
233
- }
234
-
235
- /* 自定义滚动条 */
236
- ::-webkit-scrollbar {
237
- width: 6px;
238
- }
239
-
240
- ::-webkit-scrollbar-track {
241
- background: rgba(255, 255, 255, 0.1);
242
- border-radius: 3px;
243
- }
244
-
245
- ::-webkit-scrollbar-thumb {
246
- background: rgba(255, 255, 255, 0.3);
247
- border-radius: 3px;
248
- }
249
-
250
- ::-webkit-scrollbar-thumb:hover {
251
- background: rgba(255, 255, 255, 0.5);
252
- }
253
- </style>
254
- </head>
255
- <body>
256
- <div id="root"></div>
257
-
258
- <script type="text/babel">
259
- const { useState, useEffect, useRef, useCallback } = React;
260
- const {
261
- Layout, Card, Row, Col, Table, Tag, Button, Input, Select,
262
- Statistic, Progress, Badge, Space, Typography, Tooltip, Divider,
263
- Switch, message, Spin, Modal, Descriptions, ConfigProvider
264
- } = antd;
265
-
266
- const { Title, Text } = Typography;
267
- const { Search } = Input;
268
-
269
- function WorkersPage() {
270
- const [loading, setLoading] = useState(false);
271
- const [searchKeyword, setSearchKeyword] = useState('');
272
- const [statusFilter, setStatusFilter] = useState('all');
273
- const [showAllStats, setShowAllStats] = useState(true);
274
- const [workers, setWorkers] = useState([]);
275
- const [workerStats, setWorkerStats] = useState({
276
- total_workers: 0,
277
- online_workers: 0,
278
- offline_workers: 0,
279
- total_success_count: 0,
280
- total_failed_count: 0,
281
- total_running_tasks: 0,
282
- avg_processing_time: 0
283
- });
284
-
285
- const chartRef = useRef(null);
286
- const chartInstance = useRef(null);
287
-
288
- // 格式化时间
289
- const formatTime = useCallback((seconds) => {
290
- if (!seconds || seconds === 0) return '-';
291
- if (seconds < 1) return `${Math.round(seconds * 1000)}ms`;
292
- if (seconds < 60) return `${seconds.toFixed(2)}s`;
293
- const minutes = Math.floor(seconds / 60);
294
- const remainingSeconds = (seconds % 60).toFixed(1);
295
- return `${minutes}m${remainingSeconds}s`;
296
- }, []);
297
-
298
- // 格式化日期时间
299
- const formatDateTime = (timestamp) => {
300
- if (!timestamp) return '-';
301
- return new Date(parseFloat(timestamp) * 1000).toLocaleString('zh-CN');
302
- };
303
-
304
- // 过滤Workers
305
- const filteredWorkers = React.useMemo(() => {
306
- let filtered = workers;
307
-
308
- if (searchKeyword) {
309
- filtered = filtered.filter(worker =>
310
- worker.consumer_id.toLowerCase().includes(searchKeyword.toLowerCase()) ||
311
- worker.host.toLowerCase().includes(searchKeyword.toLowerCase()) ||
312
- (worker.queues || []).some(q => q.toLowerCase().includes(searchKeyword.toLowerCase()))
313
- );
314
- }
315
-
316
- if (statusFilter !== 'all') {
317
- filtered = filtered.filter(worker => {
318
- if (statusFilter === 'online') return worker.is_alive;
319
- if (statusFilter === 'offline') return !worker.is_alive;
320
- return true;
321
- });
322
- }
323
-
324
- return filtered;
325
- }, [workers, searchKeyword, statusFilter]);
326
-
327
- // 表格列定义
328
- const columns = [
329
- {
330
- title: 'Worker ID',
331
- dataIndex: 'consumer_id',
332
- key: 'consumer_id',
333
- width: 200,
334
- render: (id) => (
335
- <Tooltip title={id}>
336
- <code style={{fontSize: '0.8rem'}}>{id.substring(0, 16)}...</code>
337
- </Tooltip>
338
- )
339
- },
340
- {
341
- title: '状态',
342
- dataIndex: 'is_alive',
343
- key: 'is_alive',
344
- width: 100,
345
- align: 'center',
346
- render: (isAlive) => (
347
- <Tag color={isAlive ? 'success' : 'error'}>
348
- {isAlive ? '在线' : '离线'}
349
- </Tag>
350
- )
351
- },
352
- {
353
- title: '主机',
354
- dataIndex: 'host',
355
- key: 'host',
356
- width: 150,
357
- },
358
- {
359
- title: '处理队列',
360
- dataIndex: 'queues',
361
- key: 'queues',
362
- render: (queues) => (
363
- <Space size="small" wrap>
364
- {(queues || []).map((queue, idx) => (
365
- <Tag key={idx} color="blue" size="small">{queue}</Tag>
366
- ))}
367
- </Space>
368
- )
369
- },
370
- {
371
- title: '成功/失败',
372
- key: 'stats',
373
- width: 150,
374
- render: (_, record) => (
375
- <Space>
376
- <Tag color="green">{record.success_count || 0}</Tag>
377
- <span>/</span>
378
- <Tag color="red">{record.failed_count || 0}</Tag>
379
- </Space>
380
- )
381
- },
382
- {
383
- title: '执行中',
384
- dataIndex: 'running_tasks',
385
- key: 'running_tasks',
386
- width: 100,
387
- align: 'center',
388
- render: (count) => (
389
- <Tag color="orange">{count || 0}</Tag>
390
- )
391
- },
392
- {
393
- title: '平均耗时',
394
- dataIndex: 'avg_processing_time',
395
- key: 'avg_processing_time',
396
- width: 100,
397
- render: (time) => formatTime(time)
398
- },
399
- {
400
- title: '最后心跳',
401
- dataIndex: 'last_heartbeat',
402
- key: 'last_heartbeat',
403
- width: 180,
404
- render: (time) => formatDateTime(time)
405
- },
406
- {
407
- title: '操作',
408
- key: 'action',
409
- width: 100,
410
- align: 'center',
411
- render: (_, record) => (
412
- <Button size="small" onClick={() => viewWorkerDetail(record)}>
413
- 详情
414
- </Button>
415
- )
416
- }
417
- ];
418
-
419
- // 查看Worker详情
420
- const viewWorkerDetail = (worker) => {
421
- Modal.info({
422
- title: 'Worker详情',
423
- width: 800,
424
- content: (
425
- <Descriptions column={2} bordered size="small">
426
- <Descriptions.Item label="Worker ID" span={2}>
427
- <code>{worker.consumer_id}</code>
428
- </Descriptions.Item>
429
- <Descriptions.Item label="状态">
430
- <Tag color={worker.is_alive ? 'success' : 'error'}>
431
- {worker.is_alive ? '在线' : '离线'}
432
- </Tag>
433
- </Descriptions.Item>
434
- <Descriptions.Item label="主机">
435
- {worker.host}
436
- </Descriptions.Item>
437
- <Descriptions.Item label="处理队列" span={2}>
438
- {(worker.queues || []).join(', ')}
439
- </Descriptions.Item>
440
- <Descriptions.Item label="成功任务">
441
- {worker.success_count || 0}
442
- </Descriptions.Item>
443
- <Descriptions.Item label="失败任务">
444
- {worker.failed_count || 0}
445
- </Descriptions.Item>
446
- <Descriptions.Item label="执行中任务">
447
- {worker.running_tasks || 0}
448
- </Descriptions.Item>
449
- <Descriptions.Item label="平均处理时间">
450
- {formatTime(worker.avg_processing_time)}
451
- </Descriptions.Item>
452
- <Descriptions.Item label="启动时间">
453
- {formatDateTime(worker.start_time)}
454
- </Descriptions.Item>
455
- <Descriptions.Item label="最后心跳">
456
- {formatDateTime(worker.last_heartbeat)}
457
- </Descriptions.Item>
458
- </Descriptions>
459
- )
460
- });
461
- };
462
-
463
- // 加载数据
464
- const loadData = async () => {
465
- try {
466
- setLoading(true);
467
-
468
- const [workersResp, statsResp] = await Promise.all([
469
- fetch('/api/workers?stats_mode=' + (showAllStats ? 'all' : 'online')),
470
- fetch('/api/stats')
471
- ]);
472
-
473
- if (workersResp.ok) {
474
- const data = await workersResp.json();
475
- setWorkers(data.workers || []);
476
- }
477
-
478
- if (statsResp.ok) {
479
- const data = await statsResp.json();
480
- setWorkerStats({
481
- total_workers: data.total_workers || 0,
482
- online_workers: data.online_workers || 0,
483
- offline_workers: (data.total_workers || 0) - (data.online_workers || 0),
484
- total_success_count: data.total_success_count || 0,
485
- total_failed_count: data.total_failed_count || 0,
486
- total_running_tasks: data.total_running_tasks || 0,
487
- avg_processing_time: data.avg_processing_time || 0
488
- });
489
- }
490
- } catch (error) {
491
- console.error('Failed to load data:', error);
492
- message.error('加载数据失败');
493
- } finally {
494
- setLoading(false);
495
- }
496
- };
497
-
498
- // 初始化图表
499
- const initChart = () => {
500
- if (chartRef.current && !chartInstance.current) {
501
- const ctx = chartRef.current.getContext('2d');
502
- chartInstance.current = new Chart(ctx, {
503
- type: 'doughnut',
504
- data: {
505
- labels: ['在线Workers', '离线Workers'],
506
- datasets: [{
507
- data: [workerStats.online_workers, workerStats.offline_workers],
508
- backgroundColor: ['#49aa19', '#a61d24'],
509
- borderWidth: 0
510
- }]
511
- },
512
- options: {
513
- responsive: true,
514
- maintainAspectRatio: false,
515
- plugins: {
516
- legend: {
517
- position: 'bottom',
518
- labels: {
519
- color: 'rgba(255, 255, 255, 0.85)',
520
- padding: 20
521
- }
522
- }
523
- }
524
- }
525
- });
526
- }
527
- };
528
-
529
- // 更新图表数据
530
- useEffect(() => {
531
- if (chartInstance.current) {
532
- chartInstance.current.data.datasets[0].data = [
533
- workerStats.online_workers,
534
- workerStats.offline_workers
535
- ];
536
- chartInstance.current.update();
537
- }
538
- }, [workerStats]);
539
-
540
- // 初始化
541
- useEffect(() => {
542
- loadData();
543
- const timer = setTimeout(initChart, 100);
544
-
545
- // 定时刷新
546
- const interval = setInterval(loadData, 5000);
547
-
548
- return () => {
549
- clearTimeout(timer);
550
- clearInterval(interval);
551
- if (chartInstance.current) {
552
- chartInstance.current.destroy();
553
- }
554
- };
555
- }, [showAllStats]);
556
-
557
- return (
558
- <div className="app-container">
559
- {/* 导航栏 */}
560
- <div className="nav-container">
561
- <div className="nav-content">
562
- <div className="nav-title">
563
- <i className="fas fa-rocket" style={{fontSize: '1.5rem'}} />
564
- Jettask Monitor
565
- </div>
566
- <Space size="large">
567
- <Button type="text" size="large" onClick={() => window.location.href = '/'}>
568
- 概览
569
- </Button>
570
- <Button type="text" size="large" onClick={() => window.location.href = '/queues.html'}>
571
- 队列
572
- </Button>
573
- <Button type="primary" size="large">
574
- Workers
575
- </Button>
576
- </Space>
577
- </div>
578
- </div>
579
-
580
- {/* 主要内容 */}
581
- <div className="main-content">
582
- {/* 统计概览 */}
583
- <Row gutter={[24, 24]} style={{marginBottom: 30}}>
584
- <Col xs={24} lg={8}>
585
- <Card className="worker-card">
586
- <Title level={4} className="gradient-text">Worker状态分布</Title>
587
- <div style={{height: 250}}>
588
- <canvas ref={chartRef}></canvas>
589
- </div>
590
- </Card>
591
- </Col>
592
- <Col xs={24} lg={16}>
593
- <Row gutter={[16, 16]}>
594
- <Col xs={12} md={8}>
595
- <Card className="worker-card">
596
- <Statistic
597
- title="总Workers"
598
- value={workerStats.total_workers}
599
- valueStyle={{color: '#177ddc'}}
600
- prefix={<i className="fas fa-server" />}
601
- />
602
- </Card>
603
- </Col>
604
- <Col xs={12} md={8}>
605
- <Card className="worker-card">
606
- <Statistic
607
- title="在线Workers"
608
- value={workerStats.online_workers}
609
- valueStyle={{color: '#49aa19'}}
610
- prefix={<i className="fas fa-check-circle" />}
611
- />
612
- </Card>
613
- </Col>
614
- <Col xs={12} md={8}>
615
- <Card className="worker-card">
616
- <Statistic
617
- title="离线Workers"
618
- value={workerStats.offline_workers}
619
- valueStyle={{color: '#a61d24'}}
620
- prefix={<i className="fas fa-times-circle" />}
621
- />
622
- </Card>
623
- </Col>
624
- <Col xs={12} md={8}>
625
- <Card className="worker-card">
626
- <Statistic
627
- title="成功任务"
628
- value={workerStats.total_success_count}
629
- valueStyle={{color: '#49aa19'}}
630
- prefix={<i className="fas fa-check" />}
631
- />
632
- </Card>
633
- </Col>
634
- <Col xs={12} md={8}>
635
- <Card className="worker-card">
636
- <Statistic
637
- title="失败任务"
638
- value={workerStats.total_failed_count}
639
- valueStyle={{color: '#a61d24'}}
640
- prefix={<i className="fas fa-times" />}
641
- />
642
- </Card>
643
- </Col>
644
- <Col xs={12} md={8}>
645
- <Card className="worker-card">
646
- <Statistic
647
- title="执行中任务"
648
- value={workerStats.total_running_tasks}
649
- valueStyle={{color: '#d89614'}}
650
- prefix={<i className="fas fa-spinner" />}
651
- />
652
- </Card>
653
- </Col>
654
- </Row>
655
- </Col>
656
- </Row>
657
-
658
- {/* Worker列表 */}
659
- <Card
660
- className="worker-card"
661
- title={
662
- <Title level={4} className="gradient-text">
663
- <i className="fas fa-users" style={{marginRight: 12}} />
664
- Worker列表
665
- </Title>
666
- }
667
- extra={
668
- <Space>
669
- <Search
670
- placeholder="搜索Worker..."
671
- allowClear
672
- value={searchKeyword}
673
- onChange={e => setSearchKeyword(e.target.value)}
674
- style={{width: 200}}
675
- />
676
- <Select
677
- value={statusFilter}
678
- onChange={setStatusFilter}
679
- style={{width: 120}}
680
- >
681
- <Select.Option value="all">全部</Select.Option>
682
- <Select.Option value="online">在线</Select.Option>
683
- <Select.Option value="offline">离线</Select.Option>
684
- </Select>
685
- <Switch
686
- checkedChildren="含历史"
687
- unCheckedChildren="仅在线"
688
- checked={showAllStats}
689
- onChange={setShowAllStats}
690
- />
691
- <Button
692
- type="primary"
693
- icon={<i className="fas fa-sync-alt" />}
694
- onClick={loadData}
695
- loading={loading}
696
- >
697
- 刷新
698
- </Button>
699
- </Space>
700
- }
701
- >
702
- <Table
703
- columns={columns}
704
- dataSource={filteredWorkers}
705
- rowKey="consumer_id"
706
- loading={loading}
707
- pagination={{
708
- defaultPageSize: 20,
709
- showSizeChanger: true,
710
- showQuickJumper: true,
711
- showTotal: (total) => `共 ${total} 个Workers`
712
- }}
713
- size="middle"
714
- />
715
- </Card>
716
- </div>
717
- </div>
718
- );
719
- }
720
-
721
- // 主应用组件
722
- function App() {
723
- return (
724
- <ConfigProvider>
725
- <WorkersPage />
726
- </ConfigProvider>
727
- );
728
- }
729
-
730
- // 渲染应用
731
- ReactDOM.render(React.createElement(App), document.getElementById('root'));
732
- </script>
733
- </body>
734
- </html>