123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380 |
- /*
- * <<
- * Davinci
- * ==
- * Copyright (C) 2016 - 2017 EDP
- * ==
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * >>
- */
- import React, { useState, useMemo, useCallback, useEffect } from 'react'
- import Helmet from 'react-helmet'
- import { connect } from 'react-redux'
- import { compose } from 'redux'
- import { Link } from 'react-router-dom'
- import { RouteComponentWithParams } from 'utils/types'
- import injectReducer from 'utils/injectReducer'
- import injectSaga from 'utils/injectSaga'
- import { createStructuredSelector } from 'reselect'
- import { makeSelectCurrentProject } from 'containers/Projects/selectors'
- import { makeSelectLoading, makeSelectSchedules } from './selectors'
- import { ScheduleActions } from './actions'
- import reducer from './reducer'
- import saga from './sagas'
- import ModulePermission from 'containers/Account/components/checkModulePermission'
- import { initializePermission } from 'containers/Account/components/checkUtilPermission'
- import { useTablePagination } from 'utils/hooks'
- import {
- Row,
- Col,
- Breadcrumb,
- Table,
- Icon,
- Button,
- Tooltip,
- Popconfirm,
- message,
- Modal
- } from 'antd'
- import { ButtonProps } from 'antd/lib/button'
- import { ColumnProps } from 'antd/lib/table'
- import Container, { ContainerTitle, ContainerBody } from 'components/Container'
- import Box from 'components/Box'
- import { ISchedule, JobStatus, IScheduleLoading } from './types'
- import { IProject } from 'containers/Projects/types'
- import utilStyles from 'assets/less/util.less'
- import Styles from './Schedule.less'
- interface IScheduleListStateProps {
- loading: IScheduleLoading
- schedules: ISchedule[]
- currentProject: IProject
- }
- interface IScheduleListDispatchProps {
- onLoadSchedules: (projectId: number) => any
- onDeleteSchedule: (id: number) => any
- onChangeScheduleJobStatus: (id: number, status: JobStatus) => any
- onExecuteScheduleImmediately: (id: number, resolve: () => void) => any
- }
- type ScheduleListProps = IScheduleListStateProps &
- IScheduleListDispatchProps &
- RouteComponentWithParams
- const JobStatusNextOperations: { [key in JobStatus]: string } = {
- new: '启动',
- failed: '重启',
- started: '暂停',
- stopped: '启动'
- }
- const JobStatusIcons: { [key in JobStatus]: string } = {
- new: 'caret-right',
- failed: 'reload',
- started: 'pause',
- stopped: 'caret-right'
- }
- const ScheduleList: React.FC<ScheduleListProps> = (props) => {
- const {
- match,
- history,
- loading,
- schedules,
- currentProject,
- onLoadSchedules,
- onDeleteSchedule,
- onChangeScheduleJobStatus,
- onExecuteScheduleImmediately
- } = props
- const [execLogModalVisible, setExecLogModalVisible] = useState(false)
- const [execLog, setExecLogContent] = useState('')
- const tablePagination = useTablePagination(0)
- const openExecLogModal = useCallback((logContent) => () => {
- setExecLogModalVisible(true)
- setExecLogContent(logContent)
- }, [])
- const closeExecLogModal = useCallback(() => {
- setExecLogModalVisible(false)
- }, [])
- const columns: Array<ColumnProps<ISchedule>> = useMemo(() => {
- return [
- {
- title: '名称',
- dataIndex: 'name',
- render: (name, record) => {
- if (!record.execLog) {
- return name
- }
- return (
- <p className={Styles.info}>
- {name}
- <Tooltip title='点击查看错误日志'>
- <Icon
- type='info-circle'
- onClick={openExecLogModal(record.execLog)}
- />
- </Tooltip>
- </p>
- )
- }
- },
- {
- title: '描述',
- dataIndex: 'description'
- },
- {
- title: '类型',
- dataIndex: 'jobType',
- width: 60,
- align: 'center'
- },
- {
- title: '有效开始时间',
- dataIndex: 'startDate',
- width: 180,
- align: 'center'
- },
- {
- title: '有效结束时间',
- dataIndex: 'endDate',
- width: 180,
- align: 'center'
- },
- {
- title: '状态',
- dataIndex: 'jobStatus',
- width: 80,
- align: 'center'
- }
- ]
- }, [])
- useEffect(() => {
- onLoadSchedules(+match.params.projectId)
- }, [])
- const addSchedule = useCallback(
- () => {
- const { projectId } = match.params
- history.push(`/project/${projectId}/schedule`)
- },
- [currentProject]
- )
- const { schedulePermission, AdminButton, EditButton } = useMemo(
- () => ({
- schedulePermission: initializePermission(
- currentProject,
- 'schedulePermission'
- ),
- AdminButton: ModulePermission<ButtonProps>(
- currentProject,
- 'schedule',
- true
- )(Button),
- EditButton: ModulePermission<ButtonProps>(
- currentProject,
- 'schedule',
- false
- )(Button)
- }),
- [currentProject]
- )
- const changeJobStatus = useCallback(
- (schedule: ISchedule) => () => {
- const { id, jobStatus } = schedule
- onChangeScheduleJobStatus(id, jobStatus)
- },
- [onChangeScheduleJobStatus]
- )
- const executeScheduleImmediately = useCallback(
- (id: number) => () => {
- onExecuteScheduleImmediately(id, () => {
- message.success('任务已开始执行')
- })
- },
- [onExecuteScheduleImmediately]
- )
- const editSchedule = useCallback(
- (scheduleId: number) => () => {
- const { projectId } = match.params
- history.push(`/project/${projectId}/schedule/${scheduleId}`)
- },
- []
- )
- const deleteSchedule = useCallback(
- (scheduleId: number) => () => {
- onDeleteSchedule(scheduleId)
- },
- [onDeleteSchedule]
- )
- const tableColumns = [...columns]
- if (schedulePermission) {
- tableColumns.push({
- title: '操作',
- key: 'action',
- align: 'center',
- width: 185,
- render: (_, record) => (
- <span className='ant-table-action-column'>
- <Tooltip title={JobStatusNextOperations[record.jobStatus]}>
- <Button
- icon={JobStatusIcons[record.jobStatus]}
- shape='circle'
- type='ghost'
- onClick={changeJobStatus(record)}
- />
- </Tooltip>
- <Popconfirm
- title='确定立即执行?'
- placement='bottom'
- onConfirm={executeScheduleImmediately(record.id)}
- >
- <Tooltip title='立即执行'>
- <Button
- shape='circle'
- type='ghost'
- >
- <i className='iconfont icon-lijitoudi' />
- </Button>
- </Tooltip>
- </Popconfirm>
- <Tooltip title='修改' trigger='hover'>
- <EditButton
- icon='edit'
- shape='circle'
- type='ghost'
- onClick={editSchedule(record.id)}
- />
- </Tooltip>
- <Popconfirm
- title='确定删除?'
- placement='bottom'
- onConfirm={deleteSchedule(record.id)}
- >
- <Tooltip title='删除'>
- <AdminButton icon='delete' shape='circle' type='ghost' />
- </Tooltip>
- </Popconfirm>
- </span>
- )
- })
- }
- return (
- <Container>
- <Helmet title='定时任务' />
- {
- !history.location.pathname.includes('dataShareService') && <ContainerTitle>
- <Row>
- <Col span={24} className={utilStyles.shortcut}>
- <Breadcrumb className={utilStyles.breadcrumb}>
- <Breadcrumb.Item>
- <Link to=''>Schedule</Link>
- </Breadcrumb.Item>
- </Breadcrumb>
- <Link to={`/account/organization/${currentProject.orgId}`}>
- <i className='iconfont icon-organization' />
- </Link>
- </Col>
- </Row>
- </ContainerTitle>
- }
- <ContainerBody>
- <Box>
- <Box.Header>
- <Box.Title>
- <Icon type='bars' />
- 定时任务列表
- </Box.Title>
- <Box.Tools>
- <Tooltip placement='bottom' title='新增'>
- <AdminButton type='primary' icon='plus' onClick={addSchedule} />
- </Tooltip>
- </Box.Tools>
- </Box.Header>
- <Box.Body>
- <Row>
- <Col span={24}>
- <Table
- rowKey='id'
- bordered
- dataSource={schedules}
- columns={tableColumns}
- pagination={tablePagination}
- loading={loading.table}
- />
- </Col>
- </Row>
- </Box.Body>
- </Box>
- </ContainerBody>
- <Modal
- title='错误日志'
- wrapClassName='ant-modal-large'
- visible={execLogModalVisible}
- onCancel={closeExecLogModal}
- footer={false}
- >
- {execLog}
- </Modal>
- </Container>
- )
- }
- const mapStateToProps = createStructuredSelector({
- schedules: makeSelectSchedules(),
- currentProject: makeSelectCurrentProject(),
- loading: makeSelectLoading()
- })
- function mapDispatchToProps(dispatch) {
- return {
- onLoadSchedules: (projectId) =>
- dispatch(ScheduleActions.loadSchedules(projectId)),
- onDeleteSchedule: (id) => dispatch(ScheduleActions.deleteSchedule(id)),
- onChangeScheduleJobStatus: (id, currentStatus) =>
- dispatch(ScheduleActions.changeSchedulesStatus(id, currentStatus)),
- onExecuteScheduleImmediately: (id, resolve) =>
- dispatch(ScheduleActions.executeScheduleImmediately(id, resolve))
- }
- }
- const withConnect = connect(
- mapStateToProps,
- mapDispatchToProps
- )
- const withReducer = injectReducer({ key: 'schedule', reducer })
- const withSaga = injectSaga({ key: 'schedule', saga })
- export default compose(
- withReducer,
- withSaga,
- withConnect
- )(ScheduleList)
|