pages_device_alert_rule.tsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  1. import React, { useState, useEffect } from 'react';
  2. import {
  3. Button, Table, Space,
  4. Form, Input, Select, message, Modal,
  5. Card, Row, Col, Switch,
  6. Popconfirm, Tag, Collapse,
  7. } from 'antd';
  8. import 'dayjs/locale/zh-cn';
  9. // 从share/types.ts导入所有类型,包括MapMode
  10. import type {
  11. ZichanInfo, DeviceAlertRule,
  12. } from '../share/monitorTypes.ts';
  13. import {
  14. AlertLevel, MetricType,
  15. AlertLevelNameMap, MetricTypeNameMap
  16. } from '../share/monitorTypes.ts';
  17. import {
  18. EnableStatus, EnableStatusNameMap,
  19. } from '../share/types.ts';
  20. import { getEnumOptions } from './utils.ts';
  21. import { DeviceInstanceAPI, DeviceAlertRuleAPI} from './api/index.ts';
  22. // 设备告警规则页面
  23. export const DeviceAlertRulePage = () => {
  24. const [loading, setLoading] = useState(false);
  25. const [ruleData, setRuleData] = useState<DeviceAlertRule[]>([]);
  26. const [pagination, setPagination] = useState({
  27. current: 1,
  28. pageSize: 10,
  29. total: 0,
  30. });
  31. const [deviceOptions, setDeviceOptions] = useState<{label: string, value: number}[]>([]);
  32. const [modalVisible, setModalVisible] = useState(false);
  33. const [modalTitle, setModalTitle] = useState('新增告警规则');
  34. const [currentRecord, setCurrentRecord] = useState<DeviceAlertRule | null>(null);
  35. const [formRef] = Form.useForm();
  36. const [modalForm] = Form.useForm();
  37. useEffect(() => {
  38. fetchDeviceOptions();
  39. fetchRuleData();
  40. }, [pagination.current, pagination.pageSize]);
  41. const fetchDeviceOptions = async () => {
  42. try {
  43. const response = await DeviceInstanceAPI.getDeviceInstances();
  44. if (response && response.data) {
  45. const options = response.data.map((device: ZichanInfo) => ({
  46. label: device.asset_name || `设备${device.id}`,
  47. value: device.id
  48. }));
  49. setDeviceOptions(options);
  50. }
  51. } catch (error) {
  52. console.error('获取设备列表失败:', error);
  53. message.error('获取设备列表失败');
  54. }
  55. };
  56. const fetchRuleData = async () => {
  57. setLoading(true);
  58. try {
  59. const values = formRef.getFieldsValue();
  60. const params = {
  61. page: pagination.current,
  62. pageSize: pagination.pageSize,
  63. device_id: values.device_id,
  64. metric_type: values.metric_type,
  65. alert_level: values.alert_level,
  66. is_enabled: values.is_enabled,
  67. };
  68. const response = await DeviceAlertRuleAPI.getDeviceAlertRules(params);
  69. if (response) {
  70. setRuleData(response.data || []);
  71. setPagination({
  72. ...pagination,
  73. total: response.total || 0,
  74. });
  75. }
  76. } catch (error) {
  77. console.error('获取告警规则失败:', error);
  78. message.error('获取告警规则失败');
  79. } finally {
  80. setLoading(false);
  81. }
  82. };
  83. const handleSearch = (values: any) => {
  84. setPagination({
  85. ...pagination,
  86. current: 1,
  87. });
  88. fetchRuleData();
  89. };
  90. const handleTableChange = (newPagination: any) => {
  91. setPagination({
  92. ...pagination,
  93. current: newPagination.current,
  94. pageSize: newPagination.pageSize,
  95. });
  96. };
  97. const handleAdd = () => {
  98. setModalTitle('新增告警规则');
  99. setCurrentRecord(null);
  100. modalForm.resetFields();
  101. setModalVisible(true);
  102. };
  103. const handleEdit = (record: DeviceAlertRule) => {
  104. setModalTitle('编辑告警规则');
  105. setCurrentRecord(record);
  106. modalForm.setFieldsValue(record);
  107. setModalVisible(true);
  108. };
  109. const handleDelete = async (id: number) => {
  110. try {
  111. await DeviceAlertRuleAPI.deleteDeviceAlertRule(id);
  112. message.success('删除成功');
  113. fetchRuleData();
  114. } catch (error) {
  115. console.error('删除失败:', error);
  116. message.error('删除失败');
  117. }
  118. };
  119. const handleEnableChange = async (record: DeviceAlertRule, enabled: boolean) => {
  120. try {
  121. await DeviceAlertRuleAPI.updateDeviceAlertRule(record.id, {
  122. is_enabled: enabled ? EnableStatus.ENABLED : EnableStatus.DISABLED
  123. });
  124. message.success(`${enabled ? '启用' : '禁用'}成功`);
  125. fetchRuleData();
  126. } catch (error) {
  127. console.error('操作失败:', error);
  128. message.error('操作失败');
  129. }
  130. };
  131. const handleModalSubmit = async () => {
  132. try {
  133. const values = await modalForm.validateFields();
  134. if (currentRecord) {
  135. // 更新
  136. await DeviceAlertRuleAPI.updateDeviceAlertRule(currentRecord.id, values);
  137. message.success('更新成功');
  138. } else {
  139. // 新增
  140. await DeviceAlertRuleAPI.createDeviceAlertRule(values);
  141. message.success('添加成功');
  142. }
  143. setModalVisible(false);
  144. fetchRuleData();
  145. } catch (error) {
  146. console.error('操作失败:', error);
  147. message.error('操作失败');
  148. }
  149. };
  150. const metricTypeOptions = getEnumOptions(MetricType, MetricTypeNameMap);
  151. const alertLevelOptions = getEnumOptions(AlertLevel, AlertLevelNameMap);
  152. const enableStatusOptions = getEnumOptions(EnableStatus, EnableStatusNameMap);
  153. const getAlertLevelTag = (level: AlertLevel) => {
  154. switch (level) {
  155. case AlertLevel.MINOR:
  156. return <Tag color="blue">次要</Tag>;
  157. case AlertLevel.NORMAL:
  158. return <Tag color="green">一般</Tag>;
  159. case AlertLevel.IMPORTANT:
  160. return <Tag color="orange">重要</Tag>;
  161. case AlertLevel.URGENT:
  162. return <Tag color="red">紧急</Tag>;
  163. default:
  164. return <Tag>未知</Tag>;
  165. }
  166. };
  167. const columns = [
  168. {
  169. title: '规则ID',
  170. dataIndex: 'id',
  171. key: 'id',
  172. width: 80,
  173. },
  174. {
  175. title: '设备',
  176. dataIndex: 'device_id',
  177. key: 'device_id',
  178. render: (id: number) => {
  179. const device = deviceOptions.find(opt => opt.value === id);
  180. return device ? device.label : id;
  181. },
  182. },
  183. {
  184. title: '监控指标',
  185. dataIndex: 'metric_type',
  186. key: 'metric_type',
  187. render: (text: string) => {
  188. const option = metricTypeOptions.find(opt => opt.value === text);
  189. return option ? option.label : text;
  190. },
  191. },
  192. {
  193. title: '最小阈值',
  194. dataIndex: 'min_value',
  195. key: 'min_value',
  196. },
  197. {
  198. title: '最大阈值',
  199. dataIndex: 'max_value',
  200. key: 'max_value',
  201. },
  202. {
  203. title: '持续时间(秒)',
  204. dataIndex: 'duration_seconds',
  205. key: 'duration_seconds',
  206. },
  207. {
  208. title: '告警等级',
  209. dataIndex: 'alert_level',
  210. key: 'alert_level',
  211. render: (level: AlertLevel) => getAlertLevelTag(level),
  212. },
  213. {
  214. title: '状态',
  215. dataIndex: 'is_enabled',
  216. key: 'is_enabled',
  217. render: (status: EnableStatus, record: DeviceAlertRule) => (
  218. <Switch
  219. checked={status === EnableStatus.ENABLED}
  220. onChange={(checked) => handleEnableChange(record, checked)}
  221. checkedChildren="启用"
  222. unCheckedChildren="禁用"
  223. />
  224. ),
  225. },
  226. {
  227. title: '操作',
  228. key: 'action',
  229. render: (_: any, record: DeviceAlertRule) => (
  230. <Space size="middle">
  231. <Button size="small" type="primary" onClick={() => handleEdit(record)}>
  232. 编辑
  233. </Button>
  234. <Popconfirm
  235. title="确定删除此规则?"
  236. onConfirm={() => handleDelete(record.id)}
  237. okText="确定"
  238. cancelText="取消"
  239. >
  240. <Button size="small" danger>
  241. 删除
  242. </Button>
  243. </Popconfirm>
  244. </Space>
  245. ),
  246. },
  247. ];
  248. return (
  249. <div>
  250. <Card title="设备告警规则" style={{ marginBottom: 16 }}>
  251. <Form
  252. form={formRef}
  253. layout="inline"
  254. onFinish={handleSearch}
  255. style={{ marginBottom: 16 }}
  256. >
  257. <Form.Item name="device_id" label="设备">
  258. <Select
  259. placeholder="选择设备"
  260. style={{ width: 200 }}
  261. allowClear
  262. options={deviceOptions}
  263. />
  264. </Form.Item>
  265. <Form.Item name="metric_type" label="监控指标">
  266. <Select
  267. placeholder="选择监控指标"
  268. style={{ width: 150 }}
  269. allowClear
  270. options={metricTypeOptions}
  271. />
  272. </Form.Item>
  273. <Form.Item name="alert_level" label="告警等级">
  274. <Select
  275. placeholder="选择告警等级"
  276. style={{ width: 120 }}
  277. allowClear
  278. options={alertLevelOptions}
  279. />
  280. </Form.Item>
  281. <Form.Item name="is_enabled" label="状态">
  282. <Select
  283. placeholder="选择状态"
  284. style={{ width: 100 }}
  285. allowClear
  286. options={enableStatusOptions}
  287. />
  288. </Form.Item>
  289. <Form.Item>
  290. <Button type="primary" htmlType="submit">
  291. 查询
  292. </Button>
  293. </Form.Item>
  294. <Form.Item>
  295. <Button type="primary" onClick={handleAdd}>
  296. 新增
  297. </Button>
  298. </Form.Item>
  299. </Form>
  300. <Table
  301. columns={columns}
  302. dataSource={ruleData}
  303. rowKey="id"
  304. pagination={pagination}
  305. loading={loading}
  306. onChange={handleTableChange}
  307. />
  308. </Card>
  309. <Modal
  310. title={modalTitle}
  311. open={modalVisible}
  312. onOk={handleModalSubmit}
  313. onCancel={() => setModalVisible(false)}
  314. width={600}
  315. >
  316. <Form
  317. form={modalForm}
  318. layout="vertical"
  319. >
  320. <Form.Item
  321. name="device_id"
  322. label="设备"
  323. rules={[{ required: true, message: '请选择设备' }]}
  324. >
  325. <Select
  326. placeholder="选择设备"
  327. options={deviceOptions}
  328. />
  329. </Form.Item>
  330. <Form.Item
  331. name="metric_type"
  332. label="监控指标"
  333. rules={[{ required: true, message: '请选择监控指标' }]}
  334. >
  335. <Select
  336. placeholder="选择监控指标"
  337. options={metricTypeOptions}
  338. />
  339. </Form.Item>
  340. <Row gutter={16}>
  341. <Col span={12}>
  342. <Form.Item
  343. name="min_value"
  344. label="最小阈值"
  345. tooltip="指标值低于此阈值时触发告警,例如CPU使用率低于5%时告警。如不需要可留空。"
  346. >
  347. <Input type="number" placeholder="输入最小阈值,例如:温度5、湿度20" />
  348. </Form.Item>
  349. </Col>
  350. <Col span={12}>
  351. <Form.Item
  352. name="max_value"
  353. label="最大阈值"
  354. tooltip="指标值高于此阈值时触发告警,例如CPU使用率高于90%时告警。如不需要可留空。"
  355. >
  356. <Input type="number" placeholder="输入最大阈值,例如:温度30、CPU使用率90" />
  357. </Form.Item>
  358. </Col>
  359. </Row>
  360. <Collapse defaultActiveKey={[]} style={{ marginBottom: 16 }}>
  361. <Collapse.Panel
  362. header="阈值参考 (点击展开查看)"
  363. key="1"
  364. >
  365. <div>
  366. <p>常见阈值参考:</p>
  367. <ul>
  368. <li>温度(temperature):最大阈值建议设为 30-35℃</li>
  369. <li>湿度(humidity):最小阈值20%,最大阈值80%</li>
  370. <li>CPU使用率(cpu_usage):最大阈值建议设为 85-95%</li>
  371. <li>内存使用率(memory_usage):最大阈值建议设为 85-95%</li>
  372. <li>磁盘使用率(disk_usage):最大阈值建议设为 85-95%</li>
  373. <li>Ping时间(ping_time):最大阈值建议设为 100-200ms</li>
  374. <li>丢包率(packet_loss):最大阈值建议设为 2-5%</li>
  375. <li>连接状态(connection_status):最大阈值建议设为 0(0表示已连接,大于0表示未连接)</li>
  376. </ul>
  377. <p>注意:最小值或最大值必须至少填写一项。</p>
  378. </div>
  379. </Collapse.Panel>
  380. </Collapse>
  381. <Form.Item
  382. name="duration_seconds"
  383. label="持续时间(秒)"
  384. rules={[{ required: true, message: '请输入持续时间' }]}
  385. initialValue={60}
  386. >
  387. <Input type="number" placeholder="输入持续时间" />
  388. </Form.Item>
  389. <Form.Item
  390. name="alert_level"
  391. label="告警等级"
  392. rules={[{ required: true, message: '请选择告警等级' }]}
  393. >
  394. <Select
  395. placeholder="选择告警等级"
  396. options={alertLevelOptions}
  397. />
  398. </Form.Item>
  399. <Form.Item
  400. name="alert_message"
  401. label="告警消息模板"
  402. >
  403. <Input.TextArea rows={3} placeholder="输入告警消息模板,可使用{{变量}}格式插入动态内容" />
  404. </Form.Item>
  405. <Form.Item
  406. name="is_enabled"
  407. label="状态"
  408. initialValue={EnableStatus.ENABLED}
  409. >
  410. <Select
  411. placeholder="选择状态"
  412. options={enableStatusOptions}
  413. />
  414. </Form.Item>
  415. </Form>
  416. </Modal>
  417. </div>
  418. );
  419. };