jettask 0.2.1__py3-none-any.whl → 0.2.4__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 (89) hide show
  1. jettask/constants.py +213 -0
  2. jettask/core/app.py +525 -205
  3. jettask/core/cli.py +193 -185
  4. jettask/core/consumer_manager.py +126 -34
  5. jettask/core/context.py +3 -0
  6. jettask/core/enums.py +137 -0
  7. jettask/core/event_pool.py +501 -168
  8. jettask/core/message.py +147 -0
  9. jettask/core/offline_worker_recovery.py +181 -114
  10. jettask/core/task.py +10 -174
  11. jettask/core/task_batch.py +153 -0
  12. jettask/core/unified_manager_base.py +243 -0
  13. jettask/core/worker_scanner.py +54 -54
  14. jettask/executors/asyncio.py +184 -64
  15. jettask/webui/backend/config.py +51 -0
  16. jettask/webui/backend/data_access.py +2083 -92
  17. jettask/webui/backend/data_api.py +3294 -0
  18. jettask/webui/backend/dependencies.py +261 -0
  19. jettask/webui/backend/init_meta_db.py +158 -0
  20. jettask/webui/backend/main.py +1358 -69
  21. jettask/webui/backend/main_unified.py +78 -0
  22. jettask/webui/backend/main_v2.py +394 -0
  23. jettask/webui/backend/namespace_api.py +295 -0
  24. jettask/webui/backend/namespace_api_old.py +294 -0
  25. jettask/webui/backend/namespace_data_access.py +611 -0
  26. jettask/webui/backend/queue_backlog_api.py +727 -0
  27. jettask/webui/backend/queue_stats_v2.py +521 -0
  28. jettask/webui/backend/redis_monitor_api.py +476 -0
  29. jettask/webui/backend/unified_api_router.py +1601 -0
  30. jettask/webui/db_init.py +204 -32
  31. jettask/webui/frontend/package-lock.json +492 -1
  32. jettask/webui/frontend/package.json +4 -1
  33. jettask/webui/frontend/src/App.css +105 -7
  34. jettask/webui/frontend/src/App.jsx +49 -20
  35. jettask/webui/frontend/src/components/NamespaceSelector.jsx +166 -0
  36. jettask/webui/frontend/src/components/QueueBacklogChart.jsx +298 -0
  37. jettask/webui/frontend/src/components/QueueBacklogTrend.jsx +638 -0
  38. jettask/webui/frontend/src/components/QueueDetailsTable.css +65 -0
  39. jettask/webui/frontend/src/components/QueueDetailsTable.jsx +487 -0
  40. jettask/webui/frontend/src/components/QueueDetailsTableV2.jsx +465 -0
  41. jettask/webui/frontend/src/components/ScheduledTaskFilter.jsx +423 -0
  42. jettask/webui/frontend/src/components/TaskFilter.jsx +425 -0
  43. jettask/webui/frontend/src/components/TimeRangeSelector.css +21 -0
  44. jettask/webui/frontend/src/components/TimeRangeSelector.jsx +160 -0
  45. jettask/webui/frontend/src/components/layout/AppLayout.css +95 -0
  46. jettask/webui/frontend/src/components/layout/AppLayout.jsx +49 -0
  47. jettask/webui/frontend/src/components/layout/Header.css +34 -10
  48. jettask/webui/frontend/src/components/layout/Header.jsx +31 -23
  49. jettask/webui/frontend/src/components/layout/SideMenu.css +137 -0
  50. jettask/webui/frontend/src/components/layout/SideMenu.jsx +209 -0
  51. jettask/webui/frontend/src/components/layout/TabsNav.css +244 -0
  52. jettask/webui/frontend/src/components/layout/TabsNav.jsx +206 -0
  53. jettask/webui/frontend/src/components/layout/UserInfo.css +197 -0
  54. jettask/webui/frontend/src/components/layout/UserInfo.jsx +197 -0
  55. jettask/webui/frontend/src/contexts/NamespaceContext.jsx +72 -0
  56. jettask/webui/frontend/src/contexts/TabsContext.backup.jsx +245 -0
  57. jettask/webui/frontend/src/main.jsx +1 -0
  58. jettask/webui/frontend/src/pages/Alerts.jsx +684 -0
  59. jettask/webui/frontend/src/pages/Dashboard.jsx +1330 -0
  60. jettask/webui/frontend/src/pages/QueueDetail.jsx +1109 -10
  61. jettask/webui/frontend/src/pages/QueueMonitor.jsx +236 -115
  62. jettask/webui/frontend/src/pages/Queues.jsx +5 -1
  63. jettask/webui/frontend/src/pages/ScheduledTasks.jsx +809 -0
  64. jettask/webui/frontend/src/pages/Settings.jsx +800 -0
  65. jettask/webui/frontend/src/services/api.js +7 -5
  66. jettask/webui/frontend/src/utils/suppressWarnings.js +22 -0
  67. jettask/webui/frontend/src/utils/userPreferences.js +154 -0
  68. jettask/webui/multi_namespace_consumer.py +543 -0
  69. jettask/webui/pg_consumer.py +983 -246
  70. jettask/webui/static/dist/assets/index-7129cfe1.css +1 -0
  71. jettask/webui/static/dist/assets/index-8d1935cc.js +774 -0
  72. jettask/webui/static/dist/index.html +2 -2
  73. jettask/webui/task_center.py +216 -0
  74. jettask/webui/task_center_client.py +150 -0
  75. jettask/webui/unified_consumer_manager.py +193 -0
  76. {jettask-0.2.1.dist-info → jettask-0.2.4.dist-info}/METADATA +1 -1
  77. jettask-0.2.4.dist-info/RECORD +134 -0
  78. jettask/webui/pg_consumer_slow.py +0 -1099
  79. jettask/webui/pg_consumer_test.py +0 -678
  80. jettask/webui/static/dist/assets/index-823408e8.css +0 -1
  81. jettask/webui/static/dist/assets/index-9968b0b8.js +0 -543
  82. jettask/webui/test_pg_consumer_recovery.py +0 -547
  83. jettask/webui/test_recovery_simple.py +0 -492
  84. jettask/webui/test_self_recovery.py +0 -467
  85. jettask-0.2.1.dist-info/RECORD +0 -91
  86. {jettask-0.2.1.dist-info → jettask-0.2.4.dist-info}/WHEEL +0 -0
  87. {jettask-0.2.1.dist-info → jettask-0.2.4.dist-info}/entry_points.txt +0 -0
  88. {jettask-0.2.1.dist-info → jettask-0.2.4.dist-info}/licenses/LICENSE +0 -0
  89. {jettask-0.2.1.dist-info → jettask-0.2.4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,684 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { Card, Table, Button, Space, Tag, message, Modal, Form, Input, Select, Switch, InputNumber, Row, Col, Statistic, Tabs, Timeline, Badge } from 'antd';
3
+ import { PlusOutlined, EditOutlined, DeleteOutlined, ReloadOutlined, BellOutlined, WarningOutlined, CheckCircleOutlined, CloseCircleOutlined, HistoryOutlined, ExpandOutlined } from '@ant-design/icons';
4
+ import dayjs from 'dayjs';
5
+ import axios from 'axios';
6
+
7
+ const { Option } = Select;
8
+ const { TextArea } = Input;
9
+ const { TabPane } = Tabs;
10
+
11
+ // 告警级别配置
12
+ const ALERT_LEVEL_CONFIG = {
13
+ 'critical': { label: '严重', color: 'red', icon: <CloseCircleOutlined /> },
14
+ 'warning': { label: '警告', color: 'orange', icon: <WarningOutlined /> },
15
+ 'info': { label: '信息', color: 'blue', icon: <BellOutlined /> },
16
+ };
17
+
18
+ // 告警类型配置
19
+ const ALERT_TYPE_CONFIG = {
20
+ 'error_rate': { label: '错误率', description: '任务错误率超过阈值' },
21
+ 'queue_length': { label: '队列长度', description: '队列积压任务超过阈值' },
22
+ 'worker_count': { label: 'Worker数量', description: 'Worker数量低于阈值' },
23
+ 'execution_time': { label: '执行时长', description: '任务执行时间超过阈值' },
24
+ 'failure_count': { label: '失败次数', description: '连续失败次数超过阈值' },
25
+ 'throughput': { label: '吞吐量', description: '任务处理速率低于阈值' },
26
+ 'idle_time': { label: '空闲时间', description: 'Worker空闲时间超过阈值' },
27
+ 'memory_usage': { label: '内存使用', description: 'Worker内存使用超过阈值' },
28
+ };
29
+
30
+ // 比较操作符
31
+ const OPERATORS = {
32
+ 'gt': '大于',
33
+ 'gte': '大于等于',
34
+ 'lt': '小于',
35
+ 'lte': '小于等于',
36
+ 'eq': '等于',
37
+ 'ne': '不等于',
38
+ };
39
+
40
+ function Alerts() {
41
+ const [loading, setLoading] = useState(false);
42
+ const [alertRules, setAlertRules] = useState([]);
43
+ const [alertHistory, setAlertHistory] = useState([]);
44
+ const [modalVisible, setModalVisible] = useState(false);
45
+ const [historyModalVisible, setHistoryModalVisible] = useState(false);
46
+ const [isEditMode, setIsEditMode] = useState(false);
47
+ const [selectedRule, setSelectedRule] = useState(null);
48
+ const [form] = Form.useForm();
49
+ const [statistics, setStatistics] = useState({
50
+ totalRules: 0,
51
+ activeRules: 0,
52
+ todayAlerts: 0,
53
+ criticalAlerts: 0,
54
+ });
55
+ const [activeTab, setActiveTab] = useState('rules');
56
+
57
+ // 获取告警规则列表
58
+ const fetchAlertRules = async () => {
59
+ setLoading(true);
60
+ try {
61
+ const response = await axios.get('/api/alert-rules');
62
+ if (response.data.success) {
63
+ setAlertRules(response.data.data);
64
+ calculateStatistics(response.data.data);
65
+ }
66
+ } catch (error) {
67
+ message.error('获取告警规则失败');
68
+ console.error('Failed to fetch alert rules:', error);
69
+ } finally {
70
+ setLoading(false);
71
+ }
72
+ };
73
+
74
+ // 获取告警历史
75
+ const fetchAlertHistory = async (ruleId = null) => {
76
+ try {
77
+ // 使用正确的API端点
78
+ if (ruleId) {
79
+ const response = await axios.get(`/api/alert-rules/${ruleId}/history`);
80
+ if (response.data.success) {
81
+ setAlertHistory(response.data.data);
82
+ }
83
+ } else {
84
+ // 暂时设置为空,因为后端还没有全局历史API
85
+ setAlertHistory([]);
86
+ }
87
+ } catch (error) {
88
+ message.error('获取告警历史失败');
89
+ console.error('Failed to fetch alert history:', error);
90
+ }
91
+ };
92
+
93
+ // 计算统计数据
94
+ const calculateStatistics = (rules) => {
95
+ const stats = {
96
+ totalRules: rules.length,
97
+ activeRules: rules.filter(r => r.is_active).length,
98
+ todayAlerts: 0, // 需要从后端获取
99
+ criticalAlerts: 0, // 需要从后端获取
100
+ };
101
+ setStatistics(stats);
102
+ };
103
+
104
+ // 初始化加载
105
+ useEffect(() => {
106
+ fetchAlertRules();
107
+ fetchAlertHistory();
108
+ }, []);
109
+
110
+ // 处理添加/编辑规则
111
+ const handleAddOrEditRule = () => {
112
+ form.validateFields().then(async (values) => {
113
+ try {
114
+ // 处理webhook_headers
115
+ if (values.webhook_headers) {
116
+ try {
117
+ values.webhook_headers = JSON.parse(values.webhook_headers);
118
+ } catch (e) {
119
+ message.error('Webhook头部格式错误,请输入有效的JSON');
120
+ return;
121
+ }
122
+ }
123
+
124
+ // 处理额外配置
125
+ if (values.extra_config) {
126
+ try {
127
+ values.extra_config = JSON.parse(values.extra_config);
128
+ } catch (e) {
129
+ message.error('额外配置格式错误,请输入有效的JSON');
130
+ return;
131
+ }
132
+ }
133
+
134
+ const url = isEditMode
135
+ ? `/api/alert-rules/${selectedRule.id}`
136
+ : '/api/alert-rules';
137
+ const method = isEditMode ? 'put' : 'post';
138
+
139
+ const response = await axios[method](url, values);
140
+ if (response.data.success) {
141
+ message.success(isEditMode ? '规则更新成功' : '规则创建成功');
142
+ setModalVisible(false);
143
+ form.resetFields();
144
+ fetchAlertRules();
145
+ }
146
+ } catch (error) {
147
+ message.error(isEditMode ? '更新规则失败' : '创建规则失败');
148
+ console.error('Failed to save rule:', error);
149
+ }
150
+ });
151
+ };
152
+
153
+ // 处理删除规则
154
+ const handleDeleteRule = (rule) => {
155
+ Modal.confirm({
156
+ title: '确认删除',
157
+ content: `确定要删除告警规则 "${rule.name}" 吗?`,
158
+ onOk: async () => {
159
+ try {
160
+ const response = await axios.delete(`/api/alert-rules/${rule.id}`);
161
+ if (response.data.success) {
162
+ message.success('规则删除成功');
163
+ fetchAlertRules();
164
+ }
165
+ } catch (error) {
166
+ message.error('删除规则失败');
167
+ console.error('Failed to delete rule:', error);
168
+ }
169
+ },
170
+ });
171
+ };
172
+
173
+ // 处理启用/禁用规则
174
+ const handleToggleRule = async (rule) => {
175
+ try {
176
+ const response = await axios.put(`/api/alert-rules/${rule.id}/toggle`, {
177
+ enabled: !rule.enabled,
178
+ });
179
+ if (response.data.success) {
180
+ message.success(rule.enabled ? '规则已禁用' : '规则已启用');
181
+ fetchAlertRules();
182
+ }
183
+ } catch (error) {
184
+ message.error('操作失败');
185
+ console.error('Failed to toggle rule:', error);
186
+ }
187
+ };
188
+
189
+ // 测试告警规则
190
+ const handleTestRule = async (rule) => {
191
+ try {
192
+ const response = await axios.post(`/api/alert-rules/${rule.id}/test`);
193
+ if (response.data.success) {
194
+ message.success('测试告警已发送');
195
+ }
196
+ } catch (error) {
197
+ message.error('测试失败');
198
+ console.error('Failed to test rule:', error);
199
+ }
200
+ };
201
+
202
+ // 打开添加/编辑模态框
203
+ const openModal = (rule = null) => {
204
+ setIsEditMode(!!rule);
205
+ setSelectedRule(rule);
206
+ if (rule) {
207
+ form.setFieldsValue({
208
+ ...rule,
209
+ webhook_headers: rule.webhook_headers ? JSON.stringify(rule.webhook_headers) : '{}',
210
+ extra_config: rule.extra_config ? JSON.stringify(rule.extra_config) : '{}',
211
+ });
212
+ } else {
213
+ form.resetFields();
214
+ }
215
+ setModalVisible(true);
216
+ };
217
+
218
+ // 查看规则历史
219
+ const viewRuleHistory = (rule) => {
220
+ setSelectedRule(rule);
221
+ fetchAlertHistory(rule.id);
222
+ setHistoryModalVisible(true);
223
+ };
224
+
225
+ // 规则表格列定义
226
+ const ruleColumns = [
227
+ {
228
+ title: '规则名称',
229
+ dataIndex: 'name',
230
+ key: 'name',
231
+ width: 200,
232
+ },
233
+ {
234
+ title: '类型',
235
+ dataIndex: 'alert_type',
236
+ key: 'alert_type',
237
+ width: 120,
238
+ render: (type) => {
239
+ const config = ALERT_TYPE_CONFIG[type] || { label: type };
240
+ return config.label;
241
+ },
242
+ },
243
+ {
244
+ title: '级别',
245
+ dataIndex: 'level',
246
+ key: 'level',
247
+ width: 80,
248
+ render: (level) => {
249
+ const config = ALERT_LEVEL_CONFIG[level] || { label: level, color: 'default' };
250
+ return <Tag color={config.color}>{config.label}</Tag>;
251
+ },
252
+ },
253
+ {
254
+ title: '条件',
255
+ key: 'condition',
256
+ width: 200,
257
+ render: (_, record) => {
258
+ const operator = OPERATORS[record.operator] || record.operator;
259
+ const scope = record.scope === 'queue' ? `队列: ${record.queue_name || '全部'}` : '全局';
260
+ return (
261
+ <div>
262
+ <div>{scope}</div>
263
+ <div style={{ fontSize: 12, color: '#666' }}>
264
+ {`${ALERT_TYPE_CONFIG[record.alert_type]?.label} ${operator} ${record.threshold}`}
265
+ {record.time_window && ` (${record.time_window}秒内)`}
266
+ </div>
267
+ </div>
268
+ );
269
+ },
270
+ },
271
+ {
272
+ title: 'Webhook',
273
+ dataIndex: 'webhook_url',
274
+ key: 'webhook_url',
275
+ width: 200,
276
+ ellipsis: true,
277
+ render: (url) => url ? <span style={{ fontSize: 12 }}>{url}</span> : '-',
278
+ },
279
+ {
280
+ title: '状态',
281
+ dataIndex: 'enabled',
282
+ key: 'enabled',
283
+ width: 80,
284
+ render: (enabled) => (
285
+ <Badge status={enabled ? 'success' : 'default'} text={enabled ? '启用' : '禁用'} />
286
+ ),
287
+ },
288
+ {
289
+ title: '最后触发',
290
+ dataIndex: 'last_triggered',
291
+ key: 'last_triggered',
292
+ width: 150,
293
+ render: (time) => time ? dayjs(time).format('MM-DD HH:mm:ss') : '-',
294
+ },
295
+ {
296
+ title: '操作',
297
+ key: 'actions',
298
+ width: 200,
299
+ fixed: 'right',
300
+ render: (_, record) => (
301
+ <Space size="small">
302
+ <Button
303
+ type="link"
304
+ size="small"
305
+ onClick={() => handleToggleRule(record)}
306
+ >
307
+ {record.enabled ? '禁用' : '启用'}
308
+ </Button>
309
+ <Button
310
+ type="link"
311
+ size="small"
312
+ onClick={() => handleTestRule(record)}
313
+ >
314
+ 测试
315
+ </Button>
316
+ <Button
317
+ type="link"
318
+ size="small"
319
+ onClick={() => viewRuleHistory(record)}
320
+ >
321
+ 历史
322
+ </Button>
323
+ <Button
324
+ type="link"
325
+ size="small"
326
+ onClick={() => openModal(record)}
327
+ >
328
+ 编辑
329
+ </Button>
330
+ <Button
331
+ type="link"
332
+ size="small"
333
+ danger
334
+ onClick={() => handleDeleteRule(record)}
335
+ >
336
+ 删除
337
+ </Button>
338
+ </Space>
339
+ ),
340
+ },
341
+ ];
342
+
343
+ // 告警历史表格列
344
+ const historyColumns = [
345
+ {
346
+ title: '触发时间',
347
+ dataIndex: 'triggered_at',
348
+ key: 'triggered_at',
349
+ width: 150,
350
+ render: (time) => dayjs(time).format('YYYY-MM-DD HH:mm:ss'),
351
+ },
352
+ {
353
+ title: '规则名称',
354
+ dataIndex: 'rule_name',
355
+ key: 'rule_name',
356
+ width: 200,
357
+ },
358
+ {
359
+ title: '级别',
360
+ dataIndex: 'level',
361
+ key: 'level',
362
+ width: 80,
363
+ render: (level) => {
364
+ const config = ALERT_LEVEL_CONFIG[level] || { label: level, color: 'default' };
365
+ return <Tag color={config.color} icon={config.icon}>{config.label}</Tag>;
366
+ },
367
+ },
368
+ {
369
+ title: '告警内容',
370
+ dataIndex: 'message',
371
+ key: 'message',
372
+ ellipsis: true,
373
+ },
374
+ {
375
+ title: '通知状态',
376
+ dataIndex: 'notification_status',
377
+ key: 'notification_status',
378
+ width: 100,
379
+ render: (status) => (
380
+ <Tag color={status === 'success' ? 'green' : 'red'}>
381
+ {status === 'success' ? '已发送' : '发送失败'}
382
+ </Tag>
383
+ ),
384
+ },
385
+ ];
386
+
387
+ return (
388
+ <div className="page-wrapper">
389
+
390
+ {/* 统计卡片 */}
391
+ <Row gutter={16} style={{ marginBottom: 16 }}>
392
+ <Col span={6}>
393
+ <Card>
394
+ <Statistic title="规则总数" value={statistics.totalRules} />
395
+ </Card>
396
+ </Col>
397
+ <Col span={6}>
398
+ <Card>
399
+ <Statistic
400
+ title="活跃规则"
401
+ value={statistics.activeRules}
402
+ valueStyle={{ color: '#3f8600' }}
403
+ />
404
+ </Card>
405
+ </Col>
406
+ <Col span={6}>
407
+ <Card>
408
+ <Statistic
409
+ title="今日告警"
410
+ value={statistics.todayAlerts}
411
+ suffix="次"
412
+ valueStyle={{ color: '#ff9800' }}
413
+ />
414
+ </Card>
415
+ </Col>
416
+ <Col span={6}>
417
+ <Card>
418
+ <Statistic
419
+ title="严重告警"
420
+ value={statistics.criticalAlerts}
421
+ suffix="个"
422
+ valueStyle={{ color: '#f50' }}
423
+ />
424
+ </Card>
425
+ </Col>
426
+ </Row>
427
+
428
+ {/* 主内容区域 */}
429
+ <Card>
430
+ <Tabs activeKey={activeTab} onChange={setActiveTab}>
431
+ <TabPane tab="告警规则" key="rules">
432
+ <div style={{ marginBottom: 16 }}>
433
+ <Space>
434
+ <Button
435
+ icon={<ReloadOutlined />}
436
+ onClick={fetchAlertRules}
437
+ >
438
+ 刷新
439
+ </Button>
440
+ <Button
441
+ type="primary"
442
+ icon={<PlusOutlined />}
443
+ onClick={() => openModal()}
444
+ >
445
+ 添加规则
446
+ </Button>
447
+ </Space>
448
+ </div>
449
+ <Table
450
+ columns={ruleColumns}
451
+ dataSource={alertRules}
452
+ rowKey="id"
453
+ loading={loading}
454
+ scroll={{ x: 1300 }}
455
+ pagination={{
456
+ pageSize: 10,
457
+ showSizeChanger: true,
458
+ showTotal: (total) => `共 ${total} 条`,
459
+ }}
460
+ />
461
+ </TabPane>
462
+
463
+ <TabPane tab="告警历史" key="history">
464
+ <div style={{ marginBottom: 16 }}>
465
+ <Button
466
+ icon={<ReloadOutlined />}
467
+ onClick={() => fetchAlertHistory()}
468
+ >
469
+ 刷新
470
+ </Button>
471
+ </div>
472
+ <Table
473
+ columns={historyColumns}
474
+ dataSource={alertHistory}
475
+ rowKey="id"
476
+ pagination={{
477
+ pageSize: 20,
478
+ showSizeChanger: true,
479
+ showTotal: (total) => `共 ${total} 条`,
480
+ }}
481
+ />
482
+ </TabPane>
483
+ </Tabs>
484
+ </Card>
485
+
486
+ {/* 添加/编辑规则模态框 */}
487
+ <Modal
488
+ title={isEditMode ? '编辑告警规则' : '添加告警规则'}
489
+ open={modalVisible}
490
+ onOk={handleAddOrEditRule}
491
+ onCancel={() => {
492
+ setModalVisible(false);
493
+ form.resetFields();
494
+ }}
495
+ width={700}
496
+ >
497
+ <Form
498
+ form={form}
499
+ layout="vertical"
500
+ initialValues={{
501
+ enabled: true,
502
+ level: 'warning',
503
+ scope: 'global',
504
+ operator: 'gt',
505
+ time_window: 300,
506
+ webhook_headers: '{}',
507
+ extra_config: '{}',
508
+ }}
509
+ >
510
+ <Row gutter={16}>
511
+ <Col span={12}>
512
+ <Form.Item
513
+ name="name"
514
+ label="规则名称"
515
+ rules={[{ required: true, message: '请输入规则名称' }]}
516
+ >
517
+ <Input placeholder="如:队列积压告警" />
518
+ </Form.Item>
519
+ </Col>
520
+ <Col span={12}>
521
+ <Form.Item
522
+ name="level"
523
+ label="告警级别"
524
+ rules={[{ required: true, message: '请选择告警级别' }]}
525
+ >
526
+ <Select>
527
+ <Option value="info">信息</Option>
528
+ <Option value="warning">警告</Option>
529
+ <Option value="critical">严重</Option>
530
+ </Select>
531
+ </Form.Item>
532
+ </Col>
533
+ </Row>
534
+
535
+ <Row gutter={16}>
536
+ <Col span={12}>
537
+ <Form.Item
538
+ name="alert_type"
539
+ label="告警类型"
540
+ rules={[{ required: true, message: '请选择告警类型' }]}
541
+ >
542
+ <Select>
543
+ {Object.entries(ALERT_TYPE_CONFIG).map(([key, config]) => (
544
+ <Option key={key} value={key}>
545
+ {config.label}
546
+ </Option>
547
+ ))}
548
+ </Select>
549
+ </Form.Item>
550
+ </Col>
551
+ <Col span={12}>
552
+ <Form.Item
553
+ name="scope"
554
+ label="监控范围"
555
+ rules={[{ required: true, message: '请选择监控范围' }]}
556
+ >
557
+ <Select onChange={(value) => {
558
+ if (value === 'global') {
559
+ form.setFieldsValue({ queue_name: undefined });
560
+ }
561
+ }}>
562
+ <Option value="global">全局</Option>
563
+ <Option value="queue">指定队列</Option>
564
+ </Select>
565
+ </Form.Item>
566
+ </Col>
567
+ </Row>
568
+
569
+ <Form.Item noStyle shouldUpdate={(prevValues, currentValues) => prevValues.scope !== currentValues.scope}>
570
+ {({ getFieldValue }) => {
571
+ return getFieldValue('scope') === 'queue' ? (
572
+ <Form.Item
573
+ name="queue_name"
574
+ label="队列名称"
575
+ rules={[{ required: true, message: '请输入队列名称' }]}
576
+ >
577
+ <Input placeholder="输入要监控的队列名称" />
578
+ </Form.Item>
579
+ ) : null;
580
+ }}
581
+ </Form.Item>
582
+
583
+ <Row gutter={16}>
584
+ <Col span={8}>
585
+ <Form.Item
586
+ name="operator"
587
+ label="比较操作"
588
+ rules={[{ required: true, message: '请选择比较操作' }]}
589
+ >
590
+ <Select>
591
+ {Object.entries(OPERATORS).map(([key, label]) => (
592
+ <Option key={key} value={key}>{label}</Option>
593
+ ))}
594
+ </Select>
595
+ </Form.Item>
596
+ </Col>
597
+ <Col span={8}>
598
+ <Form.Item
599
+ name="threshold"
600
+ label="阈值"
601
+ rules={[{ required: true, message: '请输入阈值' }]}
602
+ >
603
+ <InputNumber style={{ width: '100%' }} placeholder="如:80" />
604
+ </Form.Item>
605
+ </Col>
606
+ <Col span={8}>
607
+ <Form.Item
608
+ name="time_window"
609
+ label="时间窗口(秒)"
610
+ rules={[{ required: true, message: '请输入时间窗口' }]}
611
+ >
612
+ <InputNumber min={1} style={{ width: '100%' }} placeholder="300" />
613
+ </Form.Item>
614
+ </Col>
615
+ </Row>
616
+
617
+ <Form.Item
618
+ name="description"
619
+ label="规则描述"
620
+ >
621
+ <TextArea rows={2} placeholder="描述这个告警规则的用途" />
622
+ </Form.Item>
623
+
624
+ <Form.Item
625
+ name="webhook_url"
626
+ label="Webhook URL"
627
+ rules={[{ type: 'url', message: '请输入有效的URL' }]}
628
+ >
629
+ <Input placeholder="https://example.com/webhook" />
630
+ </Form.Item>
631
+
632
+ <Form.Item
633
+ name="webhook_headers"
634
+ label="Webhook Headers (JSON)"
635
+ extra={'例如: {"Authorization": "Bearer token"}'}
636
+ >
637
+ <TextArea rows={2} placeholder="{}" />
638
+ </Form.Item>
639
+
640
+ <Form.Item
641
+ name="extra_config"
642
+ label="额外配置 (JSON)"
643
+ extra="其他自定义配置项"
644
+ >
645
+ <TextArea rows={2} placeholder="{}" />
646
+ </Form.Item>
647
+
648
+ <Form.Item
649
+ name="enabled"
650
+ label="启用状态"
651
+ valuePropName="checked"
652
+ >
653
+ <Switch checkedChildren="启用" unCheckedChildren="禁用" />
654
+ </Form.Item>
655
+ </Form>
656
+ </Modal>
657
+
658
+ {/* 规则历史模态框 */}
659
+ <Modal
660
+ title={`告警历史 - ${selectedRule?.name}`}
661
+ open={historyModalVisible}
662
+ onCancel={() => {
663
+ setHistoryModalVisible(false);
664
+ setAlertHistory([]);
665
+ }}
666
+ width={900}
667
+ footer={null}
668
+ >
669
+ <Table
670
+ columns={historyColumns}
671
+ dataSource={alertHistory}
672
+ rowKey="id"
673
+ pagination={{
674
+ pageSize: 10,
675
+ showSizeChanger: true,
676
+ }}
677
+ />
678
+ </Modal>
679
+
680
+ </div>
681
+ );
682
+ }
683
+
684
+ export default Alerts;