index.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. import { PageContainer } from '@ant-design/pro-layout';
  2. import { Button, Card, Checkbox, Col, Form, Input, InputNumber, Row, Select, Tooltip } from 'antd';
  3. import { useEffect, useState } from 'react';
  4. import { service, StreamModel } from '@/pages/media/Stream';
  5. import { useParams } from 'umi';
  6. import { QuestionCircleOutlined } from '@ant-design/icons';
  7. import SipComponent from '@/components/SipComponent';
  8. import { onlyMessage } from '@/utils/util';
  9. import useLocation from '@/hooks/route/useLocation';
  10. interface RTPComponentProps {
  11. onChange?: (data: any) => void;
  12. value?: {
  13. rtpIp?: string;
  14. rtpPort?: number;
  15. dynamicRtpPort?: boolean;
  16. dynamicRtpPortRange?: number[];
  17. };
  18. }
  19. const RTPComponent = (props: RTPComponentProps) => {
  20. const { value, onChange } = props;
  21. const [checked, setChecked] = useState<boolean>(value?.dynamicRtpPort || false);
  22. const [data, setData] = useState<any>(value);
  23. useEffect(() => {
  24. setData(value);
  25. setChecked(!!value?.dynamicRtpPort);
  26. }, [value]);
  27. return (
  28. <div style={{ display: 'flex', alignItems: 'center' }}>
  29. <Input
  30. style={{ maxWidth: 400 }}
  31. placeholder="请输入RTP IP"
  32. value={data?.rtpIp}
  33. onChange={(e) => {
  34. if (onChange) {
  35. const item = {
  36. ...data,
  37. rtpIp: e.target.value,
  38. };
  39. setData(item);
  40. onChange(item);
  41. }
  42. }}
  43. />
  44. {!checked ? (
  45. <InputNumber
  46. style={{ minWidth: 150, margin: '0 10px' }}
  47. min={1}
  48. max={65535}
  49. value={data?.rtpPort}
  50. placeholder="请输入端口"
  51. onChange={(e) => {
  52. if (onChange) {
  53. const item = {
  54. ...data,
  55. rtpPort: e,
  56. };
  57. setData(item);
  58. onChange(item);
  59. }
  60. }}
  61. />
  62. ) : (
  63. <div style={{ margin: '0 10px' }}>
  64. <InputNumber
  65. style={{ minWidth: 150 }}
  66. min={1}
  67. max={65535}
  68. placeholder="起始端口"
  69. value={data?.dynamicRtpPortRange?.[0]}
  70. onChange={(e) => {
  71. if (onChange) {
  72. const item = {
  73. ...data,
  74. dynamicRtpPortRange: [e, data?.dynamicRtpPortRange?.[1]],
  75. };
  76. setData(item);
  77. onChange(item);
  78. }
  79. }}
  80. />{' '}
  81. 至{' '}
  82. <InputNumber
  83. style={{ minWidth: 150 }}
  84. min={1}
  85. max={65535}
  86. placeholder="终止端口"
  87. value={data?.dynamicRtpPortRange?.[1]}
  88. onChange={(e) => {
  89. if (onChange) {
  90. const item = {
  91. ...data,
  92. dynamicRtpPortRange: [data?.dynamicRtpPortRange?.[0], e],
  93. };
  94. setData(item);
  95. onChange(item);
  96. }
  97. }}
  98. />
  99. </div>
  100. )}
  101. <Checkbox
  102. checked={data?.dynamicRtpPort}
  103. onChange={(e) => {
  104. setChecked(e.target.checked);
  105. if (onChange) {
  106. const item = {
  107. ...data,
  108. dynamicRtpPort: e.target.checked,
  109. };
  110. setData(item);
  111. onChange(item);
  112. }
  113. }}
  114. >
  115. 动态端口
  116. </Checkbox>
  117. </div>
  118. );
  119. };
  120. const Detail = () => {
  121. const params = useParams<{ id: string }>();
  122. const [form] = Form.useForm();
  123. const [providers, setProviders] = useState<any[]>([]);
  124. const locations = useLocation();
  125. const [view, setView] = useState<boolean>(false);
  126. useEffect(() => {
  127. if (locations && locations.state) {
  128. setView(locations.state.view);
  129. }
  130. }, [locations]);
  131. useEffect(() => {
  132. service.queryProviders().then((resp) => {
  133. if (resp.status === 200) {
  134. setProviders(resp.result);
  135. }
  136. });
  137. if (params.id && params.id != ':id') {
  138. service.detail(params.id).then((resp) => {
  139. if (resp.status === 200) {
  140. StreamModel.current = resp.result;
  141. const data = {
  142. name: StreamModel.current?.name,
  143. provider: StreamModel.current?.provider,
  144. secret: StreamModel.current?.configuration?.secret,
  145. api: {
  146. host: StreamModel.current.configuration?.apiHost,
  147. port: StreamModel.current.configuration?.apiPort,
  148. },
  149. rtp: {
  150. rtpIp: StreamModel.current.configuration?.rtpIp,
  151. rtpPort: StreamModel.current.configuration?.rtpPort,
  152. dynamicRtpPort: StreamModel.current.configuration?.dynamicRtpPort || false,
  153. dynamicRtpPortRange: StreamModel.current.configuration?.dynamicRtpPortRange || [],
  154. },
  155. };
  156. form.setFieldsValue(data);
  157. }
  158. });
  159. }
  160. }, [params.id]);
  161. const regDomain = /[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+\.?/;
  162. const checkSIP = (_: any, value: { host: string; port: number }) => {
  163. if (!value) {
  164. return Promise.resolve();
  165. }
  166. if (!value || !value.host) {
  167. return Promise.reject(new Error('请输入API HOST'));
  168. } else if (value?.host && !regDomain.test(value.host)) {
  169. return Promise.reject(new Error('请输入正确的IP地址或者域名'));
  170. } else if (!value?.port) {
  171. return Promise.reject(new Error('请输入端口'));
  172. } else if ((value?.port && Number(value.port) < 1) || Number(value.port) > 65535) {
  173. return Promise.reject(new Error('端口请输入1~65535之间的正整数'));
  174. }
  175. return Promise.resolve();
  176. };
  177. const testPort = (value: any) => {
  178. return (value && Number(value) < 1) || Number(value) > 65535;
  179. };
  180. const checkRIP = (
  181. _: any,
  182. value: {
  183. rtpIp: string;
  184. rtpPort: number;
  185. dynamicRtpPort: boolean;
  186. dynamicRtpPortRange: number[];
  187. },
  188. ) => {
  189. if (!value) {
  190. return Promise.resolve();
  191. }
  192. if (!value || !value.rtpIp) {
  193. return Promise.reject(new Error('请输入RTP IP'));
  194. } else if (value?.rtpIp && !regDomain.test(value.rtpIp)) {
  195. return Promise.reject(new Error('请输入正确的IP地址或者域名'));
  196. } else if (!value.dynamicRtpPort) {
  197. if (value.rtpIp && !regDomain.test(value.rtpIp)) {
  198. return Promise.reject(new Error('请输入正确的IP地址或者域名'));
  199. }
  200. if (!value?.rtpPort) {
  201. return Promise.reject(new Error('请输入端口'));
  202. }
  203. if (testPort(value?.rtpPort)) {
  204. return Promise.reject(new Error('端口请输入1~65535之间的正整数'));
  205. }
  206. } else if (value.dynamicRtpPort) {
  207. if (value.dynamicRtpPortRange) {
  208. if (!value.dynamicRtpPortRange?.[0]) {
  209. return Promise.reject(new Error('请输入起始端口'));
  210. } else if (testPort(value.dynamicRtpPortRange?.[0])) {
  211. return Promise.reject(new Error('端口请输入1~65535之间的正整数'));
  212. }
  213. if (!value.dynamicRtpPortRange?.[1]) {
  214. return Promise.reject(new Error('请输入终止端口'));
  215. } else if (testPort(value.dynamicRtpPortRange?.[1])) {
  216. return Promise.reject(new Error('端口请输入1~65535之间的正整数'));
  217. }
  218. if (
  219. value.dynamicRtpPortRange?.[0] &&
  220. value.dynamicRtpPortRange?.[1] &&
  221. value.dynamicRtpPortRange?.[0] > value.dynamicRtpPortRange?.[1]
  222. ) {
  223. return Promise.reject(new Error('终止端口需大于等于起始端'));
  224. }
  225. } else if (!value.dynamicRtpPortRange) {
  226. return Promise.reject(new Error('请输入端口'));
  227. }
  228. }
  229. return Promise.resolve();
  230. };
  231. return (
  232. <PageContainer>
  233. <Card>
  234. <Form
  235. layout="vertical"
  236. form={form}
  237. onFinish={async (values: any) => {
  238. const param = {
  239. name: values.name,
  240. provider: values.provider,
  241. configuration: {
  242. secret: values?.secret,
  243. apiHost: values.api?.host,
  244. apiPort: values.api?.port,
  245. rtpIp: values.rtp?.rtpIp,
  246. rtpPort: values.rtp?.rtpPort,
  247. dynamicRtpPort: values.rtp?.dynamicRtpPort,
  248. dynamicRtpPortRange: values.rtp?.dynamicRtpPortRange || [],
  249. },
  250. };
  251. let resp = undefined;
  252. if (params.id && params.id !== ':id') {
  253. resp = await service.update({ ...param, id: params.id });
  254. } else {
  255. resp = await service.save(param);
  256. }
  257. if (resp && resp.status === 200) {
  258. onlyMessage('操作成功!');
  259. history.back();
  260. }
  261. }}
  262. >
  263. <Row gutter={24}>
  264. <Col span={12}>
  265. <Form.Item
  266. label="流媒体名称"
  267. name="name"
  268. rules={[
  269. {
  270. required: true,
  271. message: '请输入流媒体名称',
  272. },
  273. {
  274. max: 64,
  275. message: '最多输入64个字符',
  276. },
  277. ]}
  278. >
  279. <Input placeholder="请输入流媒体名称" />
  280. </Form.Item>
  281. </Col>
  282. <Col span={12}>
  283. <Form.Item
  284. label="服务商"
  285. name="provider"
  286. rules={[{ required: true, message: '请选择服务商' }]}
  287. >
  288. <Select placeholder="请选择服务商">
  289. {providers.map((item) => (
  290. <Select.Option key={item.id} value={item.id}>
  291. {item.name}
  292. </Select.Option>
  293. ))}
  294. </Select>
  295. </Form.Item>
  296. </Col>
  297. <Col span={12}>
  298. <Form.Item
  299. label="密钥"
  300. name="secret"
  301. rules={[
  302. {
  303. max: 64,
  304. message: '最多输入64个字符',
  305. },
  306. ]}
  307. >
  308. <Input.Password placeholder="请输入密钥" />
  309. </Form.Item>
  310. </Col>
  311. <Col span={12}>
  312. <Form.Item
  313. label={
  314. <span>
  315. API Host
  316. <Tooltip style={{ marginLeft: 5 }} title="调用流媒体接口时请求的服务地址">
  317. <QuestionCircleOutlined />
  318. </Tooltip>
  319. </span>
  320. }
  321. name="api"
  322. rules={[{ required: true }, { validator: checkSIP }]}
  323. >
  324. <SipComponent />
  325. </Form.Item>
  326. </Col>
  327. <Col span={24}>
  328. <Form.Item
  329. label={
  330. <span>
  331. RTP IP
  332. <Tooltip
  333. style={{ marginLeft: 5 }}
  334. title="视频设备将流推送到该IP地址下,部分设备仅支持IP地址,建议全是用IP地址"
  335. >
  336. <QuestionCircleOutlined />
  337. </Tooltip>
  338. </span>
  339. }
  340. name="rtp"
  341. rules={[{ required: true }, { validator: checkRIP }]}
  342. >
  343. <RTPComponent />
  344. </Form.Item>
  345. </Col>
  346. <Col span={24}>
  347. <Form.Item>
  348. {!view && (
  349. <Button type="primary" htmlType="submit">
  350. 保存
  351. </Button>
  352. )}
  353. </Form.Item>
  354. </Col>
  355. </Row>
  356. </Form>
  357. </Card>
  358. </PageContainer>
  359. );
  360. };
  361. export default Detail;