index.tsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  1. import { PageContainer } from '@ant-design/pro-layout';
  2. import { Button, Card, Col, Divider, Image, Row, Table, Tooltip } from 'antd';
  3. import TitleComponent from '@/components/TitleComponent';
  4. import { createSchemaField } from '@formily/react';
  5. import { ArrayItems, Form, FormButtonGroup, FormGrid, FormItem, Input } from '@formily/antd';
  6. import type { ISchema } from '@formily/json-schema';
  7. import { useMemo, useState } from 'react';
  8. import { createForm, onFormInit } from '@formily/core';
  9. import FLevelInput from '@/components/FLevelInput';
  10. import type { IOConfigItem } from '@/pages/rule-engine/Alarm/Config/typing';
  11. import Service from '@/pages/rule-engine/Alarm/Config/service';
  12. import styles from './index.less';
  13. import ReactMarkdown from 'react-markdown';
  14. import { QuestionCircleOutlined } from '@ant-design/icons';
  15. import { onlyMessage } from '@/utils/util';
  16. export const service = new Service('alarm/config');
  17. const ioImg = require('/public/images/alarm/io.png');
  18. const Config = () => {
  19. const [tab, setTab] = useState<'io' | 'config' | string>('config');
  20. const outputData = [
  21. {
  22. key: 'alarmName',
  23. name: '告警名称',
  24. type: 'string',
  25. desc: '推送的告警名称',
  26. },
  27. {
  28. key: 'id',
  29. name: '告警ID',
  30. type: 'string',
  31. desc: '告警唯一性标识',
  32. },
  33. {
  34. key: 'targetType',
  35. name: '告警类型',
  36. type: 'string',
  37. desc: '告警所属的业务类型,具体有产品、设备、部门、其他',
  38. },
  39. {
  40. key: 'targetId',
  41. name: '告警目标ID',
  42. type: 'string',
  43. desc: '告警目标唯一性标识',
  44. },
  45. {
  46. key: 'targetName',
  47. name: '告警目标名称',
  48. type: 'string',
  49. desc: '告警目标实例名称',
  50. },
  51. {
  52. key: 'alarmDate',
  53. name: '最近告警时间',
  54. type: 'date',
  55. desc: '最近一次的告警触发时间',
  56. },
  57. {
  58. key: 'level',
  59. name: '告警级别',
  60. type: 'int',
  61. desc: '告警严重程度指标',
  62. },
  63. {
  64. key: 'state',
  65. name: '告警状态',
  66. type: 'object',
  67. desc: 'value:告警状态 text:告警值',
  68. },
  69. {
  70. key: 'description',
  71. name: '告警说明',
  72. type: 'string',
  73. desc: '告警规则说明',
  74. },
  75. ];
  76. const outputColumns = [
  77. {
  78. title: '名称',
  79. dataIndex: 'name',
  80. key: 'name',
  81. ellipsis: true,
  82. },
  83. {
  84. title: '标识',
  85. dataIndex: 'key',
  86. key: 'key',
  87. ellipsis: true,
  88. },
  89. {
  90. title: '类型',
  91. dataIndex: 'type',
  92. key: 'type',
  93. ellipsis: true,
  94. },
  95. {
  96. title: '说明',
  97. dataIndex: 'desc',
  98. key: 'desc',
  99. ellipsis: true,
  100. },
  101. ];
  102. const subData = [
  103. {
  104. key: 'id',
  105. name: '告警ID',
  106. type: 'string',
  107. require: '是',
  108. desc: '订阅的告警唯一性标识',
  109. },
  110. {
  111. key: 'describe',
  112. name: '处理内容',
  113. type: 'string',
  114. require: '是',
  115. desc: '告警处理内容详细描述说明',
  116. },
  117. {
  118. key: 'state',
  119. name: '告警状态',
  120. type: 'string',
  121. require: '是',
  122. desc: '告警中, 无告警',
  123. },
  124. {
  125. key: 'handleTime',
  126. name: '处理时间',
  127. type: 'long',
  128. require: '否',
  129. desc: '告警处理时间,不填是默认为消息处理时间',
  130. },
  131. ];
  132. const subColumns = [...outputColumns];
  133. subColumns.splice(3, 0, {
  134. title: '必填',
  135. dataIndex: 'require',
  136. key: 'require',
  137. ellipsis: true,
  138. });
  139. const SchemaField = createSchemaField({
  140. components: {
  141. FormItem,
  142. Input,
  143. ArrayItems,
  144. FormGrid,
  145. FLevelInput,
  146. },
  147. });
  148. const levelForm = useMemo(
  149. () =>
  150. createForm({
  151. validateFirst: true,
  152. effects() {
  153. onFormInit(async (form) => {
  154. const resp: any = await service.queryLevel();
  155. if (resp.status === 200) {
  156. const _level = resp.result.levels.map(
  157. (item: { level: number; title: string }) => item.title,
  158. );
  159. while (_level.length < 5) {
  160. _level.push('');
  161. }
  162. form.setValuesIn('level', _level);
  163. }
  164. });
  165. // onFieldReact('level.0.remove', (state, f1) => {
  166. // state.setState({ display: 'none' });
  167. // f1.setFieldState('level.add', (state1) => {
  168. // const length = f1.values.level?.length;
  169. // if (length > 4) {
  170. // state1.display = 'none';
  171. // } else {
  172. // state1.display = 'visible';
  173. // }
  174. // });
  175. // });
  176. },
  177. }),
  178. [],
  179. );
  180. const inputForm = useMemo(
  181. () =>
  182. createForm({
  183. validateFirst: true,
  184. effects() {
  185. onFormInit(async (f) => {
  186. const resp = await service.getDataExchange('consume');
  187. if (resp.status === 200) {
  188. f.setInitialValues(resp.result?.config.config);
  189. f.setValuesIn('id', resp.result?.id);
  190. }
  191. });
  192. },
  193. }),
  194. [],
  195. );
  196. const outputForm = useMemo(
  197. () =>
  198. createForm({
  199. validateFirst: true,
  200. effects() {
  201. onFormInit(async (f) => {
  202. const resp = await service.getDataExchange('producer');
  203. if (resp.status === 200) {
  204. f.setInitialValues(resp.result?.config.config);
  205. f.setValuesIn('id', resp.result?.id);
  206. }
  207. });
  208. },
  209. }),
  210. [],
  211. );
  212. const levelSchema: ISchema = {
  213. type: 'object',
  214. properties: {
  215. level: {
  216. type: 'array',
  217. 'x-component': 'ArrayItems',
  218. 'x-decorator': 'FormItem',
  219. maxItems: 5,
  220. items: {
  221. type: 'void',
  222. 'x-decorator': 'FormGrid',
  223. 'x-decorator-props': {
  224. maxColumns: 24,
  225. minColumns: 24,
  226. columnGap: 2,
  227. },
  228. properties: {
  229. input: {
  230. type: 'string',
  231. 'x-decorator': 'FormItem',
  232. 'x-component': 'FLevelInput',
  233. 'x-decorator-props': {
  234. gridSpan: 23,
  235. },
  236. },
  237. // remove: {
  238. // type: 'void',
  239. // title: <div style={{ width: '20px' }} />,
  240. // 'x-decorator': 'FormItem',
  241. // 'x-component': 'ArrayItems.Remove',
  242. // 'x-decorator-props': {
  243. // gridSpan: 1,
  244. // },
  245. // 'x-component-props': {
  246. // style: { marginTop: '40px' },
  247. // },
  248. // },
  249. },
  250. },
  251. // properties: {
  252. // add: {
  253. // type: 'void',
  254. // title: '添加级别',
  255. // 'x-component': 'ArrayItems.Addition',
  256. // },
  257. // },
  258. },
  259. },
  260. };
  261. const ioSchema: ISchema = {
  262. type: 'object',
  263. properties: {
  264. id: {
  265. 'x-component': 'Input',
  266. 'x-hidden': true,
  267. },
  268. address: {
  269. title: 'kafka地址',
  270. type: 'string',
  271. required: true,
  272. 'x-decorator': 'FormItem',
  273. 'x-component': 'Input',
  274. 'x-component-props': {
  275. placeholder: '请输入kafka地址',
  276. },
  277. },
  278. topic: {
  279. title: 'topic',
  280. type: 'string',
  281. required: true,
  282. 'x-decorator': 'FormItem',
  283. 'x-component': 'Input',
  284. 'x-component-props': {
  285. placeholder: '请输入topic',
  286. },
  287. },
  288. // layout2: {
  289. // type: 'void',
  290. // 'x-decorator': 'FormGrid',
  291. // 'x-decorator-props': {
  292. // maxColumns: 2,
  293. // minColumns: 2,
  294. // columnGap: 24,
  295. // },
  296. // properties: {
  297. // username: {
  298. // title: '用户名',
  299. // type: 'string',
  300. // // required: true,
  301. // 'x-decorator': 'FormItem',
  302. // 'x-component': 'Input',
  303. // 'x-component-props': {
  304. // placeholder: '请输入用户名',
  305. // },
  306. // 'x-decorator-props': {
  307. // gridSpan: 1,
  308. // },
  309. // },
  310. // password: {
  311. // title: '密码',
  312. // type: 'string',
  313. // // required: true,
  314. // 'x-decorator': 'FormItem',
  315. // 'x-component': 'Input',
  316. // 'x-decorator-props': {
  317. // gridSpan: 1,
  318. // },
  319. // 'x-component-props': {
  320. // placeholder: '请输入密码',
  321. // },
  322. // },
  323. // },
  324. // },
  325. },
  326. };
  327. const handleSaveIO = async () => {
  328. outputForm.validate();
  329. inputForm.validate();
  330. const inputConfig: IOConfigItem = await inputForm.submit();
  331. const outputConfig: IOConfigItem = await outputForm.submit();
  332. const inputResp = await service.saveOutputData({
  333. config: {
  334. config: outputConfig,
  335. },
  336. id: outputConfig.id,
  337. sourceType: 'kafka',
  338. exchangeType: 'producer',
  339. });
  340. const outputResp = await service.saveOutputData({
  341. config: {
  342. sourceType: 'kafka',
  343. config: inputConfig,
  344. },
  345. id: inputConfig.id,
  346. sourceType: 'kafka',
  347. exchangeType: 'consume',
  348. });
  349. if (inputResp.status === 200 && outputResp.status === 200) {
  350. onlyMessage('操作成功');
  351. }
  352. };
  353. const handleSaveLevel = async () => {
  354. const values: { level: string[] } = await levelForm.submit();
  355. const _level = values?.level.map((l: string, i: number) => ({ level: i + 1, title: l }));
  356. const resp = await service.saveLevel(_level);
  357. if (resp.status === 200) {
  358. onlyMessage('操作成功');
  359. }
  360. };
  361. const outputText = `
  362. ~~~json
  363. {
  364. "id": "1518055745863864320",
  365. "alarmConfigId": "1511601633016569856",
  366. "alarmName": "一楼烟感告警",
  367. "targetType": "product",
  368. "targetId": "product-01",
  369. "targetName": "产品-001",
  370. "alarmTime": "1651233650840",
  371. "level": 3,
  372. "state": {
  373. "text": "告警中",
  374. "value": "warning"
  375. },
  376. "description": "一楼烟感告警设置"
  377. }
  378. ~~~
  379. `;
  380. const subText = `
  381. ~~~json
  382. {
  383. "id": "1518055745863864320",
  384. "state": "normal",
  385. "describe": "已处理"
  386. }
  387. ~~~
  388. `;
  389. const level = (
  390. <Row>
  391. <Col span={14}>
  392. <Card>
  393. <TitleComponent data={'告警级别配置'} />
  394. <Form form={levelForm}>
  395. <SchemaField schema={levelSchema} />
  396. <FormButtonGroup.Sticky>
  397. <FormButtonGroup.FormItem>
  398. <Button type="primary" onClick={handleSaveLevel}>
  399. 保存
  400. </Button>
  401. </FormButtonGroup.FormItem>
  402. </FormButtonGroup.Sticky>
  403. </Form>
  404. </Card>
  405. </Col>
  406. <Col span={10}>
  407. <div style={{ marginLeft: 20 }} className={styles.doc}>
  408. <h1>功能说明</h1>
  409. <div>1、告警级别用于描述告警的严重程度,请根据业务管理方式进行自定义。</div>
  410. <div>2、告警级别将会在告警配置中被引用</div>
  411. <div>3、最多可配置5个级别</div>
  412. </div>
  413. </Col>
  414. </Row>
  415. );
  416. const io = (
  417. <Row>
  418. <Col span={14}>
  419. <div>
  420. <Card>
  421. <TitleComponent
  422. data={
  423. <span>
  424. 告警数据输出
  425. <Tooltip title={'将告警数据输出到其他第三方系统'}>
  426. <QuestionCircleOutlined style={{ marginLeft: 5 }} />
  427. </Tooltip>
  428. </span>
  429. }
  430. />
  431. <Form form={outputForm} layout="vertical">
  432. <SchemaField schema={ioSchema} />
  433. </Form>
  434. <Divider />
  435. <TitleComponent
  436. data={
  437. <span>
  438. 告警处理结果输入
  439. <Tooltip title={'接收第三方系统处理的告警结果'}>
  440. <QuestionCircleOutlined style={{ marginLeft: 5 }} />
  441. </Tooltip>
  442. </span>
  443. }
  444. />
  445. <Form form={inputForm} layout="vertical">
  446. <SchemaField schema={ioSchema} />
  447. <FormButtonGroup.Sticky>
  448. <FormButtonGroup.FormItem>
  449. <Button type="primary" onClick={handleSaveIO}>
  450. 保存
  451. </Button>
  452. </FormButtonGroup.FormItem>
  453. </FormButtonGroup.Sticky>
  454. </Form>
  455. </Card>
  456. </div>
  457. </Col>
  458. <Col span={10}>
  459. <div style={{ height: 560, marginLeft: 20, paddingBottom: 24 }}>
  460. <div className={styles.doc}>
  461. <h1>功能图示</h1>
  462. <div className={styles.image}>
  463. <Image width="100%" src={ioImg} />
  464. </div>
  465. <h1>功能说明</h1>
  466. <div>
  467. 1、平台支持将告警数据输出到kafka,第三方系统可订阅kafka中的告警数据,进行业务处理。
  468. </div>
  469. <h2>输出参数</h2>
  470. <div>
  471. <Table dataSource={outputData} pagination={false} columns={outputColumns} />
  472. </div>
  473. <h2>示例</h2>
  474. <ReactMarkdown className={styles.code}>{outputText}</ReactMarkdown>
  475. <div>2、平台支持订阅kafka中告警处理数据,并更新告警记录状态。</div>
  476. <h2>订阅参数</h2>
  477. <div>
  478. <Table dataSource={subData} pagination={false} columns={subColumns} />
  479. </div>
  480. <h2>示例</h2>
  481. <ReactMarkdown className={styles.code}>{subText}</ReactMarkdown>
  482. </div>
  483. </div>
  484. </Col>
  485. </Row>
  486. );
  487. const list = [
  488. {
  489. key: 'config',
  490. tab: '告警级别',
  491. component: level,
  492. },
  493. { key: 'io', tab: '数据流转', component: io },
  494. ];
  495. return (
  496. <PageContainer onTabChange={setTab} tabActiveKey={tab} tabList={list}>
  497. {list.find((k) => k.key === tab)?.component}
  498. </PageContainer>
  499. );
  500. };
  501. export default Config;