import intl from 'react-intl-universal'; import React, { useState, useEffect, useRef } from 'react'; import { Button, message, Modal, Table, Tag, Space, Dropdown, Menu, Typography, Input, Tooltip, Checkbox, } from 'antd'; import { ClockCircleOutlined, Loading3QuartersOutlined, CloseCircleOutlined, EllipsisOutlined, CheckCircleOutlined, EditOutlined, StopOutlined, DeleteOutlined, FileTextOutlined, PauseCircleOutlined, PlayCircleOutlined, } from '@ant-design/icons'; import config from '@/utils/config'; import { PageContainer } from '@ant-design/pro-layout'; import { request } from '@/utils/http'; import SubscriptionModal from './modal'; import { history, useOutletContext } from '@umijs/max'; import './index.less'; import SubscriptionLogModal from './logModal'; import { SharedContext } from '@/layouts'; import useTableScrollHeight from '@/hooks/useTableScrollHeight'; const { Text, Paragraph } = Typography; const { Search } = Input; export enum SubscriptionStatus { 'running', 'idle', 'disabled', 'queued', } export enum IntervalSchedule { 'days' = '天', 'hours' = '时', 'minutes' = '分', 'seconds' = '秒', } export enum SubscriptionType { 'private-repo' = '私有仓库', 'public-repo' = '公开仓库', 'file' = '单文件', } const Subscription = () => { const { headerStyle, isPhone, socketMessage } = useOutletContext(); const columns: any = [ { title: intl.get('名称'), dataIndex: 'name', key: 'name', width: 150, sorter: { compare: (a: any, b: any) => a.name.localeCompare(b.name), multiple: 2, }, }, { title: intl.get('链接'), dataIndex: 'url', key: 'url', sorter: { compare: (a: any, b: any) => a.name.localeCompare(b.name), multiple: 2, }, render: (text: string, record: any) => { return ( {text} ); }, }, { title: intl.get('类型'), dataIndex: 'type', key: 'type', width: 130, render: (text: string, record: any) => { return (SubscriptionType as any)[record.type]; }, }, { title: intl.get('分支'), dataIndex: 'branch', key: 'branch', width: 130, render: (text: string, record: any) => { return record.branch || '-'; }, }, { title: intl.get('定时规则'), width: 180, render: (text: string, record: any) => { if (record.schedule_type === 'interval') { const { type, value } = record.interval_schedule; return `每${value}${(IntervalSchedule as any)[type]}`; } return record.schedule; }, }, { title: intl.get('状态'), key: 'status', dataIndex: 'status', width: 110, filters: [ { text: intl.get('运行中'), value: 0, }, { text: intl.get('空闲中'), value: 1, }, { text: intl.get('已禁用'), value: 2, }, ], onFilter: (value: number, record: any) => { if (record.is_disabled && record.status !== 0) { return value === 2; } else { return record.status === value; } }, render: (text: string, record: any) => ( <> {(!record.is_disabled || record.status !== SubscriptionStatus.idle) && ( <> {record.status === SubscriptionStatus.idle && ( } color="default"> {intl.get('空闲中')} )} {record.status === SubscriptionStatus.running && ( } color="processing" > {intl.get('运行中')} )} )} {record.is_disabled === 1 && record.status === SubscriptionStatus.idle && ( } color="error"> {intl.get('已禁用')} )} ), }, { title: intl.get('操作'), key: 'action', width: 130, render: (text: string, record: any, index: number) => { const isPc = !isPhone; return ( {record.status === SubscriptionStatus.idle && ( { e.stopPropagation(); runSubscription(record, index); }} > )} {record.status !== SubscriptionStatus.idle && ( { e.stopPropagation(); stopSubsciption(record, index); }} > )} { e.stopPropagation(); setLogSubscription({ ...record, timestamp: Date.now() }); }} > ); }, }, ]; const [value, setValue] = useState([]); const [loading, setLoading] = useState(true); const [isModalVisible, setIsModalVisible] = useState(false); const [editedSubscription, setEditedSubscription] = useState(); const [searchText, setSearchText] = useState(''); const [currentPage, setCurrentPage] = useState(1); const [pageSize, setPageSize] = useState(20); const [isLogModalVisible, setIsLogModalVisible] = useState(false); const [logSubscription, setLogSubscription] = useState(); const tableRef = useRef(null); const tableScrollHeight = useTableScrollHeight(tableRef); const deleteCheckRef = useRef(false); const runSubscription = (record: any, index: number) => { Modal.confirm({ title: intl.get('确认运行'), content: ( <> {intl.get('确认运行定时任务')}{' '} {record.name} {' '} {intl.get('吗')} ), onOk() { request .put(`${config.apiPrefix}subscriptions/run`, [record.id]) .then(({ code, data }) => { if (code === 200) { const result = [...value]; const i = result.findIndex((x) => x.id === record.id); if (i !== -1) { result.splice(i, 1, { ...record, status: SubscriptionStatus.running, }); setValue(result); } } }); }, onCancel() { console.log('Cancel'); }, }); }; const stopSubsciption = (record: any, index: number) => { Modal.confirm({ title: intl.get('确认停止'), content: ( <> {intl.get('确认停止定时任务')}{' '} {record.name} {' '} {intl.get('吗')} ), onOk() { request .put(`${config.apiPrefix}subscriptions/stop`, [record.id]) .then(({ code, data }) => { if (code === 200) { const result = [...value]; const i = result.findIndex((x) => x.id === record.id); if (i !== -1) { result.splice(i, 1, { ...record, pid: null, status: SubscriptionStatus.idle, }); setValue(result); } } }); }, onCancel() { console.log('Cancel'); }, }); }; const getSubscriptions = () => { setLoading(true); request .get(`${config.apiPrefix}subscriptions?searchValue=${searchText}`) .then(({ code, data }) => { if (code === 200) { setValue(data); setCurrentPage(1); } }) .finally(() => setLoading(false)); }; const addSubscription = () => { setEditedSubscription(null as any); setIsModalVisible(true); }; const editSubscription = (record: any, index: number) => { setEditedSubscription(record); setIsModalVisible(true); }; const onCheckChange = (e) => { deleteCheckRef.current = e.target.checked; }; const delSubscription = (record: any, index: number) => { Modal.confirm({ title: intl.get('确认删除'), content: ( <> {intl.get('确认删除定时订阅')}{' '} {record.name} {' '} {intl.get('吗')}
{intl.get('同时删除关联任务和脚本')}
), onOk() { request .delete(`${config.apiPrefix}subscriptions`, { data: [record.id], params: { force: deleteCheckRef.current }, }) .then(({ code, data }) => { if (code === 200) { message.success(intl.get('删除成功')); const result = [...value]; const i = result.findIndex((x) => x.id === record.id); if (i !== -1) { result.splice(i, 1); setValue(result); } } }); }, onCancel() { console.log('Cancel'); }, }); }; const enabledOrDisabledSubscription = (record: any, index: number) => { Modal.confirm({ title: `确认${ record.is_disabled === 1 ? intl.get('启用') : intl.get('禁用') }`, content: ( <> {intl.get('确认')} {record.is_disabled === 1 ? intl.get('启用') : intl.get('禁用')} {intl.get('定时订阅')}{' '} {record.name} {' '} {intl.get('吗')} ), onOk() { request .put( `${config.apiPrefix}subscriptions/${ record.is_disabled === 1 ? 'enable' : 'disable' }`, [record.id], ) .then(({ code, data }) => { if (code === 200) { const newStatus = record.is_disabled === 1 ? 0 : 1; const result = [...value]; const i = result.findIndex((x) => x.id === record.id); if (i !== -1) { result.splice(i, 1, { ...record, is_disabled: newStatus, }); setValue(result); } } }); }, onCancel() { console.log('Cancel'); }, }); }; const MoreBtn: React.FC<{ record: any; index: number; }> = ({ record, index }) => ( }, { label: record.is_disabled === 1 ? intl.get('启用') : intl.get('禁用'), key: 'enableOrDisable', icon: record.is_disabled === 1 ? ( ) : ( ), }, { label: intl.get('删除'), key: 'delete', icon: }, ], onClick: ({ key, domEvent }) => { domEvent.stopPropagation(); action(key, record, index); }, }} > e.stopPropagation()}> ); const action = (key: string | number, record: any, index: number) => { switch (key) { case 'edit': editSubscription(record, index); break; case 'enableOrDisable': enabledOrDisabledSubscription(record, index); break; case 'delete': delSubscription(record, index); break; default: break; } }; const handleCancel = (subscription?: any) => { setIsModalVisible(false); if (subscription) { handleSubscriptions(subscription); } }; const onSearch = (value: string) => { setSearchText(value.trim()); }; const handleSubscriptions = (subscription: any) => { const index = value.findIndex((x) => x.id === subscription.id); const result = [...value]; if (index === -1) { result.unshift(subscription); } else { result.splice(index, 1, { ...subscription, }); } setValue(result); }; const onPageChange = (page: number, pageSize: number | undefined) => { setCurrentPage(page); setPageSize(pageSize as number); localStorage.setItem('pageSize', pageSize + ''); }; const getRowClassName = (record: any, index: number) => { return record.isPinned ? 'pinned-subscription subscription' : 'subscription'; }; useEffect(() => { if (!socketMessage) return; const { type, message, references } = socketMessage; if (type === 'runSubscriptionEnd' && references.length > 0) { const result = [...value]; for (let i = 0; i < references.length; i++) { const index = value.findIndex((x) => x.id === references[i]); if (index !== -1) { result.splice(index, 1, { ...value[index], status: SubscriptionStatus.idle, }); } } setValue(result); } }, [socketMessage]); useEffect(() => { if (logSubscription) { localStorage.setItem('logSubscription', logSubscription.id); setIsLogModalVisible(true); } }, [logSubscription]); useEffect(() => { getSubscriptions(); }, [searchText]); useEffect(() => { setPageSize(parseInt(localStorage.getItem('pageSize') || '20')); }, []); return ( , , ]} header={{ style: headerStyle, }} > `第 ${range[0]}-${range[1]} 条/总共 ${total} 条`, pageSizeOptions: [20, 100, 500, 1000] as any, }} dataSource={value} rowKey="id" size="middle" scroll={{ x: 1000, y: tableScrollHeight }} loading={loading} rowClassName={getRowClassName} /> { setIsLogModalVisible(false); }} subscription={logSubscription} /> ); }; export default Subscription;