index.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. /*
  2. * <<
  3. * Davinci
  4. * ==
  5. * Copyright (C) 2016 - 2017 EDP
  6. * ==
  7. * Licensed under the Apache License, Version 2.0 (the "License");
  8. * you may not use this file except in compliance with the License.
  9. * You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing, software
  14. * distributed under the License is distributed on an "AS IS" BASIS,
  15. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. * See the License for the specific language governing permissions and
  17. * limitations under the License.
  18. * >>
  19. */
  20. import React, { useState, useMemo, useCallback, useEffect } from 'react'
  21. import Helmet from 'react-helmet'
  22. import { connect } from 'react-redux'
  23. import { compose } from 'redux'
  24. import { Link } from 'react-router-dom'
  25. import { RouteComponentWithParams } from 'utils/types'
  26. import injectReducer from 'utils/injectReducer'
  27. import injectSaga from 'utils/injectSaga'
  28. import { createStructuredSelector } from 'reselect'
  29. import { makeSelectCurrentProject } from 'containers/Projects/selectors'
  30. import { makeSelectLoading, makeSelectSchedules } from './selectors'
  31. import { ScheduleActions } from './actions'
  32. import reducer from './reducer'
  33. import saga from './sagas'
  34. import ModulePermission from 'containers/Account/components/checkModulePermission'
  35. import { initializePermission } from 'containers/Account/components/checkUtilPermission'
  36. import { useTablePagination } from 'utils/hooks'
  37. import {
  38. Row,
  39. Col,
  40. Breadcrumb,
  41. Table,
  42. Icon,
  43. Button,
  44. Tooltip,
  45. Popconfirm,
  46. message,
  47. Modal
  48. } from 'antd'
  49. import { ButtonProps } from 'antd/lib/button'
  50. import { ColumnProps } from 'antd/lib/table'
  51. import Container, { ContainerTitle, ContainerBody } from 'components/Container'
  52. import Box from 'components/Box'
  53. import { ISchedule, JobStatus, IScheduleLoading } from './types'
  54. import { IProject } from 'containers/Projects/types'
  55. import utilStyles from 'assets/less/util.less'
  56. import Styles from './Schedule.less'
  57. interface IScheduleListStateProps {
  58. loading: IScheduleLoading
  59. schedules: ISchedule[]
  60. currentProject: IProject
  61. }
  62. interface IScheduleListDispatchProps {
  63. onLoadSchedules: (projectId: number) => any
  64. onDeleteSchedule: (id: number) => any
  65. onChangeScheduleJobStatus: (id: number, status: JobStatus) => any
  66. onExecuteScheduleImmediately: (id: number, resolve: () => void) => any
  67. }
  68. type ScheduleListProps = IScheduleListStateProps &
  69. IScheduleListDispatchProps &
  70. RouteComponentWithParams
  71. const JobStatusNextOperations: { [key in JobStatus]: string } = {
  72. new: '启动',
  73. failed: '重启',
  74. started: '暂停',
  75. stopped: '启动'
  76. }
  77. const JobStatusIcons: { [key in JobStatus]: string } = {
  78. new: 'caret-right',
  79. failed: 'reload',
  80. started: 'pause',
  81. stopped: 'caret-right'
  82. }
  83. const ScheduleList: React.FC<ScheduleListProps> = (props) => {
  84. const {
  85. match,
  86. history,
  87. loading,
  88. schedules,
  89. currentProject,
  90. onLoadSchedules,
  91. onDeleteSchedule,
  92. onChangeScheduleJobStatus,
  93. onExecuteScheduleImmediately
  94. } = props
  95. const [execLogModalVisible, setExecLogModalVisible] = useState(false)
  96. const [execLog, setExecLogContent] = useState('')
  97. const tablePagination = useTablePagination(0)
  98. const openExecLogModal = useCallback((logContent) => () => {
  99. setExecLogModalVisible(true)
  100. setExecLogContent(logContent)
  101. }, [])
  102. const closeExecLogModal = useCallback(() => {
  103. setExecLogModalVisible(false)
  104. }, [])
  105. const columns: Array<ColumnProps<ISchedule>> = useMemo(() => {
  106. return [
  107. {
  108. title: '名称',
  109. dataIndex: 'name',
  110. render: (name, record) => {
  111. if (!record.execLog) {
  112. return name
  113. }
  114. return (
  115. <p className={Styles.info}>
  116. {name}
  117. <Tooltip title="点击查看错误日志">
  118. <Icon
  119. type="info-circle"
  120. onClick={openExecLogModal(record.execLog)}
  121. />
  122. </Tooltip>
  123. </p>
  124. )
  125. }
  126. },
  127. {
  128. title: '描述',
  129. dataIndex: 'description'
  130. },
  131. {
  132. title: '类型',
  133. dataIndex: 'jobType',
  134. width: 60,
  135. align: 'center'
  136. },
  137. {
  138. title: '有效开始时间',
  139. dataIndex: 'startDate',
  140. width: 180,
  141. align: 'center'
  142. },
  143. {
  144. title: '有效结束时间',
  145. dataIndex: 'endDate',
  146. width: 180,
  147. align: 'center'
  148. },
  149. {
  150. title: '状态',
  151. dataIndex: 'jobStatus',
  152. width: 80,
  153. align: 'center'
  154. }
  155. ]
  156. }, [])
  157. useEffect(() => {
  158. onLoadSchedules(+match.params.projectId)
  159. }, [])
  160. const addSchedule = useCallback(
  161. () => {
  162. const { projectId } = match.params
  163. history.push(`/project/${projectId}/schedule`)
  164. },
  165. [currentProject]
  166. )
  167. const { schedulePermission, AdminButton, EditButton } = useMemo(
  168. () => ({
  169. schedulePermission: initializePermission(
  170. currentProject,
  171. 'schedulePermission'
  172. ),
  173. AdminButton: ModulePermission<ButtonProps>(
  174. currentProject,
  175. 'schedule',
  176. true
  177. )(Button),
  178. EditButton: ModulePermission<ButtonProps>(
  179. currentProject,
  180. 'schedule',
  181. false
  182. )(Button)
  183. }),
  184. [currentProject]
  185. )
  186. const changeJobStatus = useCallback(
  187. (schedule: ISchedule) => () => {
  188. const { id, jobStatus } = schedule
  189. onChangeScheduleJobStatus(id, jobStatus)
  190. },
  191. [onChangeScheduleJobStatus]
  192. )
  193. const executeScheduleImmediately = useCallback(
  194. (id: number) => () => {
  195. onExecuteScheduleImmediately(id, () => {
  196. message.success('任务已开始执行')
  197. })
  198. },
  199. [onExecuteScheduleImmediately]
  200. )
  201. const editSchedule = useCallback(
  202. (scheduleId: number) => () => {
  203. const { projectId } = match.params
  204. history.push(`/project/${projectId}/schedule/${scheduleId}`)
  205. },
  206. []
  207. )
  208. const deleteSchedule = useCallback(
  209. (scheduleId: number) => () => {
  210. onDeleteSchedule(scheduleId)
  211. },
  212. [onDeleteSchedule]
  213. )
  214. const tableColumns = [...columns]
  215. if (schedulePermission) {
  216. tableColumns.push({
  217. title: '操作',
  218. key: 'action',
  219. align: 'center',
  220. width: 185,
  221. render: (_, record) => (
  222. <span className="ant-table-action-column">
  223. <Tooltip title={JobStatusNextOperations[record.jobStatus]}>
  224. <Button
  225. icon={JobStatusIcons[record.jobStatus]}
  226. shape="circle"
  227. type="ghost"
  228. onClick={changeJobStatus(record)}
  229. />
  230. </Tooltip>
  231. <Popconfirm
  232. title="确定立即执行?"
  233. placement="bottom"
  234. onConfirm={executeScheduleImmediately(record.id)}
  235. >
  236. <Tooltip title="立即执行">
  237. <Button
  238. shape="circle"
  239. type="ghost"
  240. >
  241. <i className="iconfont icon-lijitoudi" />
  242. </Button>
  243. </Tooltip>
  244. </Popconfirm>
  245. <Tooltip title="修改" trigger="hover">
  246. <EditButton
  247. icon="edit"
  248. shape="circle"
  249. type="ghost"
  250. onClick={editSchedule(record.id)}
  251. />
  252. </Tooltip>
  253. <Popconfirm
  254. title="确定删除?"
  255. placement="bottom"
  256. onConfirm={deleteSchedule(record.id)}
  257. >
  258. <Tooltip title="删除">
  259. <AdminButton icon="delete" shape="circle" type="ghost" />
  260. </Tooltip>
  261. </Popconfirm>
  262. </span>
  263. )
  264. })
  265. }
  266. return (
  267. <Container>
  268. <Helmet title="Schedule" />
  269. <ContainerTitle>
  270. <Row>
  271. <Col span={24} className={utilStyles.shortcut}>
  272. <Breadcrumb className={utilStyles.breadcrumb}>
  273. <Breadcrumb.Item>
  274. <Link to="">Schedule</Link>
  275. </Breadcrumb.Item>
  276. </Breadcrumb>
  277. <Link to={`/account/organization/${currentProject.orgId}`}>
  278. <i className='iconfont icon-organization' />
  279. </Link>
  280. </Col>
  281. </Row>
  282. </ContainerTitle>
  283. <ContainerBody>
  284. <Box>
  285. <Box.Header>
  286. <Box.Title>
  287. <Icon type="bars" />
  288. Schedule List
  289. </Box.Title>
  290. <Box.Tools>
  291. <Tooltip placement="bottom" title="新增">
  292. <AdminButton type="primary" icon="plus" onClick={addSchedule} />
  293. </Tooltip>
  294. </Box.Tools>
  295. </Box.Header>
  296. <Box.Body>
  297. <Row>
  298. <Col span={24}>
  299. <Table
  300. rowKey="id"
  301. bordered
  302. dataSource={schedules}
  303. columns={tableColumns}
  304. pagination={tablePagination}
  305. loading={loading.table}
  306. />
  307. </Col>
  308. </Row>
  309. </Box.Body>
  310. </Box>
  311. </ContainerBody>
  312. <Modal
  313. title="错误日志"
  314. wrapClassName="ant-modal-large"
  315. visible={execLogModalVisible}
  316. onCancel={closeExecLogModal}
  317. footer={false}
  318. >
  319. {execLog}
  320. </Modal>
  321. </Container>
  322. )
  323. }
  324. const mapStateToProps = createStructuredSelector({
  325. schedules: makeSelectSchedules(),
  326. currentProject: makeSelectCurrentProject(),
  327. loading: makeSelectLoading()
  328. })
  329. function mapDispatchToProps(dispatch) {
  330. return {
  331. onLoadSchedules: (projectId) =>
  332. dispatch(ScheduleActions.loadSchedules(projectId)),
  333. onDeleteSchedule: (id) => dispatch(ScheduleActions.deleteSchedule(id)),
  334. onChangeScheduleJobStatus: (id, currentStatus) =>
  335. dispatch(ScheduleActions.changeSchedulesStatus(id, currentStatus)),
  336. onExecuteScheduleImmediately: (id, resolve) =>
  337. dispatch(ScheduleActions.executeScheduleImmediately(id, resolve))
  338. }
  339. }
  340. const withConnect = connect(
  341. mapStateToProps,
  342. mapDispatchToProps
  343. )
  344. const withReducer = injectReducer({ key: 'schedule', reducer })
  345. const withSaga = injectSaga({ key: 'schedule', saga })
  346. export default compose(
  347. withReducer,
  348. withSaga,
  349. withConnect
  350. )(ScheduleList)