index.tsx 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  1. import TitleComponent from '@/components/TitleComponent';
  2. import { InfoCircleFilled, QuestionCircleOutlined } from '@ant-design/icons';
  3. import { PageContainer } from '@ant-design/pro-layout';
  4. import {
  5. Alert,
  6. Button,
  7. Card,
  8. Col,
  9. Form,
  10. Image,
  11. Input,
  12. InputNumber,
  13. message,
  14. Radio,
  15. Row,
  16. Select,
  17. Tooltip,
  18. } from 'antd';
  19. import SipComponent from '@/components/SipComponent';
  20. import SipSelectComponent from '@/components/SipSelectComponent';
  21. // import { testIP } from '@/utils/util';
  22. import { useEffect, useState } from 'react';
  23. import { service } from '../index';
  24. import { useLocation } from 'umi';
  25. import styles from './index.less';
  26. const Save = () => {
  27. const location: any = useLocation();
  28. const [form] = Form.useForm();
  29. const [clusters, setClusters] = useState<any[]>([]);
  30. const id = location?.query?.id || '';
  31. const [list, setList] = useState<any[]>([]);
  32. const [transport, setTransport] = useState<'UDP' | 'TCP'>('UDP');
  33. const regDomain = /[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+\.?/;
  34. const checkSIP = (_: any, value: { host: string; port: number }) => {
  35. if (!value) {
  36. return Promise.resolve();
  37. } else if (!value.host) {
  38. return Promise.reject(new Error('请输入IP 地址'));
  39. } else if (value?.host && !regDomain.test(value.host)) {
  40. return Promise.reject(new Error('请输入正确的IP地址'));
  41. } else if (!value?.port) {
  42. return Promise.reject(new Error('请输入端口'));
  43. } else if ((value?.port && Number(value.port) < 1) || Number(value.port) > 65535) {
  44. return Promise.reject(new Error('端口请输入1~65535之间的正整数'));
  45. }
  46. return Promise.resolve();
  47. };
  48. const checkLocalSIP = (_: any, value: { host: string; port: number }) => {
  49. if (!value) {
  50. return Promise.resolve();
  51. } else if (!value.host) {
  52. return Promise.reject(new Error('请选择IP地址'));
  53. } else if (!value?.port) {
  54. return Promise.reject(new Error('请选择端口'));
  55. }
  56. return Promise.resolve();
  57. };
  58. useEffect(() => {
  59. service.queryClusters().then((resp) => {
  60. if (resp.status === 200) {
  61. setClusters(resp.result);
  62. }
  63. });
  64. service.queryResources().then((resp) => {
  65. if (resp.status === 200) {
  66. setList(resp.result);
  67. }
  68. });
  69. if (!!id) {
  70. service.detail(id).then((resp) => {
  71. if (resp.status === 200) {
  72. const sipConfigs = resp.result?.sipConfigs[0];
  73. const data = {
  74. ...resp.result,
  75. sipConfigs: {
  76. ...sipConfigs,
  77. public: {
  78. host: sipConfigs.remoteAddress,
  79. port: sipConfigs.remotePort,
  80. },
  81. local: {
  82. host: sipConfigs.host,
  83. port: sipConfigs.port,
  84. },
  85. remotePublic: {
  86. host: sipConfigs.publicHost,
  87. port: sipConfigs.publicPort,
  88. },
  89. },
  90. };
  91. form.setFieldsValue(data);
  92. }
  93. });
  94. }
  95. }, []);
  96. const keepValidator = (_: any, value: any) => {
  97. if ((!value && value !== 0) || (Number(value) >= 1 && Number(value) <= 10000)) {
  98. return Promise.resolve();
  99. }
  100. return Promise.reject(new Error('请输入1~10000之间的数字'));
  101. };
  102. const img1 = require('/public/images/northbound/doc1.png');
  103. const img2 = require('/public/images/northbound/doc2.png');
  104. const img3 = require('/public/images/northbound/doc3.png');
  105. return (
  106. <PageContainer>
  107. <Card>
  108. <Row gutter={24}>
  109. <Col span={12}>
  110. <Form
  111. name="cascade"
  112. layout="vertical"
  113. form={form}
  114. initialValues={{
  115. proxyStream: false,
  116. sipConfigs: {
  117. transport: 'UDP',
  118. keepaliveInterval: 60,
  119. registerInterval: 3600,
  120. },
  121. }}
  122. onFinish={async (values: any) => {
  123. const sipConfigs = {
  124. ...values.sipConfigs,
  125. remoteAddress: values.sipConfigs.public.host,
  126. remotePort: values.sipConfigs.public.port,
  127. host: values.sipConfigs.local.host,
  128. port: values.sipConfigs.local.port,
  129. publicHost: values.sipConfigs.remotePublic.host,
  130. publicPort: values.sipConfigs.remotePublic.port,
  131. };
  132. delete values.sipConfigs;
  133. delete sipConfigs.public;
  134. delete sipConfigs.local;
  135. const param = { ...values, sipConfigs: [sipConfigs] };
  136. let resp = undefined;
  137. if (id) {
  138. resp = await service.update({ ...param, id });
  139. } else {
  140. resp = await service.save(param);
  141. }
  142. if (resp && resp.status === 200) {
  143. message.success('操作成功!');
  144. history.back();
  145. }
  146. }}
  147. >
  148. <Row gutter={24}>
  149. <TitleComponent data={'基本信息'} />
  150. <Col span={12}>
  151. <Form.Item
  152. label="名称"
  153. name="name"
  154. rules={[
  155. { required: true, message: '请输入名称' },
  156. { max: 64, message: '最大长度64字符' },
  157. ]}
  158. >
  159. <Input placeholder="请输入名称" />
  160. </Form.Item>
  161. </Col>
  162. <Col span={12}>
  163. <Form.Item
  164. label={<span>代理视频流</span>}
  165. name="proxyStream"
  166. rules={[{ required: true, message: '请选择代理视频流' }]}
  167. >
  168. <Radio.Group optionType="button" buttonStyle="solid">
  169. <Radio.Button value={true}>启用</Radio.Button>
  170. <Radio.Button value={false}>禁用</Radio.Button>
  171. </Radio.Group>
  172. </Form.Item>
  173. </Col>
  174. <TitleComponent data={'信令服务配置'} />
  175. <Col span={12}>
  176. <Form.Item
  177. label={
  178. <span>
  179. 集群节点
  180. <Tooltip title="使用此集群节点级联到上级平台">
  181. <QuestionCircleOutlined />
  182. </Tooltip>
  183. </span>
  184. }
  185. name={['sipConfigs', 'clusterNodeId']}
  186. rules={[{ required: true, message: '请选择集群节点' }]}
  187. >
  188. <Select placeholder="请选择集群节点">
  189. {clusters.map((item) => (
  190. <Select.Option key={item.id} value={item.id}>
  191. {item.name}
  192. </Select.Option>
  193. ))}
  194. </Select>
  195. </Form.Item>
  196. </Col>
  197. <Col span={12}>
  198. <Form.Item
  199. label="信令名称"
  200. name={['sipConfigs', 'name']}
  201. rules={[
  202. { required: true, message: '请输入信令名称' },
  203. { max: 64, message: '最大长度64字符' },
  204. ]}
  205. >
  206. <Input placeholder="请输入信令名称" />
  207. </Form.Item>
  208. </Col>
  209. <Col span={24}>
  210. <Form.Item
  211. label="上级SIP ID"
  212. name={['sipConfigs', 'sipId']}
  213. rules={[
  214. { required: true, message: '请输入上级SIP ID' },
  215. { max: 64, message: '最大长度64字符' },
  216. ]}
  217. >
  218. <Input placeholder="请输入上级SIP ID" />
  219. </Form.Item>
  220. </Col>
  221. <Col span={12}>
  222. <Form.Item
  223. label="上级SIP域"
  224. name={['sipConfigs', 'domain']}
  225. rules={[
  226. { required: true, message: '请输入上级平台SIP域' },
  227. { max: 64, message: '最大长度64字符' },
  228. ]}
  229. >
  230. <Input placeholder="请输入上级平台SIP域" />
  231. </Form.Item>
  232. </Col>
  233. <Col span={12}>
  234. <Form.Item
  235. label="上级SIP 地址"
  236. name={['sipConfigs', 'public']}
  237. rules={[
  238. { required: true, message: '请输入上级SIP 地址' },
  239. { validator: checkSIP },
  240. ]}
  241. >
  242. <SipComponent />
  243. </Form.Item>
  244. </Col>
  245. <Col span={24}>
  246. <Form.Item
  247. label="本地SIP ID"
  248. name={['sipConfigs', 'localSipId']}
  249. rules={[
  250. { required: true, message: '请输入网关侧的SIP ID' },
  251. { max: 64, message: '最大长度64字符' },
  252. ]}
  253. >
  254. <Input placeholder="网关侧的SIP ID" />
  255. </Form.Item>
  256. </Col>
  257. <Col span={12}>
  258. <Form.Item
  259. label={
  260. <span>
  261. SIP本地地址
  262. <Tooltip title="使用指定的网卡和端口进行请求">
  263. <QuestionCircleOutlined />
  264. </Tooltip>
  265. </span>
  266. }
  267. name={['sipConfigs', 'local']}
  268. rules={[
  269. { required: true, message: '请输入SIP本地地址' },
  270. { validator: checkLocalSIP },
  271. ]}
  272. >
  273. <SipSelectComponent data={list} transport={transport} />
  274. </Form.Item>
  275. </Col>
  276. <Col span={12}>
  277. <Form.Item
  278. label="SIP远程地址"
  279. name={['sipConfigs', 'remotePublic']}
  280. rules={[
  281. { required: true, message: '请输入SIP远程地址' },
  282. { validator: checkSIP },
  283. ]}
  284. >
  285. <SipComponent />
  286. </Form.Item>
  287. </Col>
  288. <Col span={24}>
  289. <Form.Item
  290. label="传输协议"
  291. name={['sipConfigs', 'transport']}
  292. rules={[{ required: true, message: '请选择传输协议' }]}
  293. >
  294. <Radio.Group
  295. optionType="button"
  296. buttonStyle="solid"
  297. onChange={(e) => {
  298. setTransport(e.target.value);
  299. }}
  300. >
  301. <Radio.Button value="UDP">UDP</Radio.Button>
  302. <Radio.Button value="TCP">TCP</Radio.Button>
  303. </Radio.Group>
  304. </Form.Item>
  305. </Col>
  306. <Col span={12}>
  307. <Form.Item
  308. label="用户"
  309. name={['sipConfigs', 'user']}
  310. rules={[
  311. { required: true, message: '请输入用户' },
  312. { max: 64, message: '最大长度64字符' },
  313. ]}
  314. >
  315. <Input placeholder="请输入用户" />
  316. </Form.Item>
  317. </Col>
  318. <Col span={12}>
  319. <Form.Item
  320. label="接入密码"
  321. name={['sipConfigs', 'password']}
  322. rules={[
  323. { required: true, message: '请输入接入密码' },
  324. { max: 64, message: '最大长度64字符' },
  325. ]}
  326. >
  327. <Input.Password placeholder="请输入接入密码" />
  328. </Form.Item>
  329. </Col>
  330. <Col span={12}>
  331. <Form.Item
  332. label="厂商"
  333. name={['sipConfigs', 'manufacturer']}
  334. rules={[
  335. { required: true, message: '请输入厂商' },
  336. { max: 64, message: '最大长度64字符' },
  337. ]}
  338. >
  339. <Input placeholder="请输入厂商" />
  340. </Form.Item>
  341. </Col>
  342. <Col span={12}>
  343. <Form.Item
  344. label="型号"
  345. name={['sipConfigs', 'model']}
  346. rules={[
  347. { required: true, message: '请输入型号' },
  348. { max: 64, message: '最大长度64字符' },
  349. ]}
  350. >
  351. <Input placeholder="请输入型号" />
  352. </Form.Item>
  353. </Col>
  354. <Col span={12}>
  355. <Form.Item
  356. label="版本号"
  357. name={['sipConfigs', 'firmware']}
  358. rules={[
  359. { required: true, message: '请输入版本号' },
  360. { max: 64, message: '最大长度64字符' },
  361. ]}
  362. >
  363. <Input placeholder="请输入版本号" />
  364. </Form.Item>
  365. </Col>
  366. <Col span={12}>
  367. <Form.Item
  368. label="心跳周期(秒)"
  369. name={['sipConfigs', 'keepaliveInterval']}
  370. rules={[
  371. { required: true, message: '请输入心跳周期' },
  372. { validator: keepValidator },
  373. ]}
  374. >
  375. <InputNumber placeholder="请输入心跳周期" style={{ width: '100%' }} />
  376. </Form.Item>
  377. </Col>
  378. <Col span={12}>
  379. <Form.Item
  380. label="注册间隔(秒)"
  381. name={['sipConfigs', 'registerInterval']}
  382. rules={[
  383. { required: true, message: '请输入注册间隔' },
  384. { validator: keepValidator },
  385. ]}
  386. >
  387. <InputNumber placeholder="请输入注册间隔" style={{ width: '100%' }} />
  388. </Form.Item>
  389. </Col>
  390. <Col span={24}>
  391. <Form.Item>
  392. <Button type="primary" htmlType="submit">
  393. 保存
  394. </Button>
  395. </Form.Item>
  396. </Col>
  397. </Row>
  398. </Form>
  399. </Col>
  400. <Col span={12}>
  401. <div className={styles.doc}>
  402. <h1>1.概述</h1>
  403. <div>配置国标级联,平台可以将已经接入到自身的摄像头共享给第三方调用播放。</div>
  404. <div>
  405. <Alert
  406. icon={<InfoCircleFilled style={{ fontSize: 16, marginTop: 5 }} />}
  407. description="注:该配置只用于将本平台向上级联至第三方平台,如需第三方平台向上级联至本平台,请在“视频设备”页面新增设备时选择“GB/T28181”接入方式。"
  408. showIcon
  409. />
  410. </div>
  411. <h1>2.配置说明</h1>
  412. <div>以下配置说明以将本平台数据级联到LiveGBS平台为例。</div>
  413. <h2>1、上级SIP ID</h2>
  414. <div>
  415. 请填写第三方平台中配置的<b>SIP ID</b>。
  416. </div>
  417. <div className={styles.image}>
  418. <Image width="100%" src={img2} />
  419. </div>
  420. <h2>2、上级SIP 域</h2>
  421. <div>
  422. 请填写第三方平台中配置的<b>SIP ID域</b>。
  423. </div>
  424. <div className={styles.image}>
  425. <Image width="100%" src={img1} />
  426. </div>
  427. <h2>3、上级SIP 地址</h2>
  428. <div>
  429. 请填写第三方平台中配置的<b>SIP ID地址</b>。
  430. </div>
  431. <div className={styles.image}>
  432. <Image width="100%" src={img3} />
  433. </div>
  434. <h2>4、本地SIP ID</h2>
  435. <div>
  436. 请填写本地的<b>SIP ID地址</b>。
  437. 地址由中心编码(8位)、行业编码(2位)、类型编码(3位)和序号(7位)四个码段共20位十
  438. 进制数字字符构成。详细规则请参见《GB/T28181-2016》中附录D部分。
  439. </div>
  440. <h2>5、SIP本地地址</h2>
  441. <div>
  442. 请选择<b>指定的网卡和端口</b>,如有疑问请联系系统运维人员。
  443. </div>
  444. <h2>6、用户</h2>
  445. <div>
  446. 部分平台有基于用户和接入密码的特殊认证。通常情况下,请填写<b>本地SIP ID</b>值。
  447. </div>
  448. <h2>7、接入密码</h2>
  449. <div>需与上级平台设置的接入密码一致,用于身份认证。</div>
  450. <h2>8、厂商/型号/版本号</h2>
  451. <div>
  452. 本平台将以“设备”的身份级联到上级平台,请设置本平台在上级平台中显示的厂商、型号、版本号。
  453. </div>
  454. <h2>9、心跳周期</h2>
  455. <div>需与上级平台设置的心跳周期保持一致,通常默认60秒。</div>
  456. <h2>10、注册间隔</h2>
  457. <div>
  458. 若SIP代理通过注册方式校时,其注册间隔时间宜设置为小于 SIP代理与 SIP服务器出现1s误
  459. 差所经过的运行时间。
  460. </div>
  461. </div>
  462. </Col>
  463. </Row>
  464. </Card>
  465. </PageContainer>
  466. );
  467. };
  468. export default Save;