qinglong/src/pages/crontab/detail.tsx
2022-05-08 09:41:06 +08:00

549 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useEffect, useRef, useState } from 'react';
import {
Modal,
message,
Input,
Form,
Button,
Card,
Tag,
List,
Divider,
Typography,
Tooltip,
} from 'antd';
import {
ClockCircleOutlined,
CloseCircleOutlined,
FieldTimeOutlined,
Loading3QuartersOutlined,
FileOutlined,
PlayCircleOutlined,
PauseCircleOutlined,
} from '@ant-design/icons';
import { CrontabStatus } from './index';
import { diffTime } from '@/utils/date';
import { request } from '@/utils/http';
import config from '@/utils/config';
import CronLogModal from './logModal';
import Editor from '@monaco-editor/react';
import IconFont from '@/components/iconfont';
const { Text } = Typography;
const tabList = [
{
key: 'log',
tab: '日志',
},
{
key: 'script',
tab: '脚本',
},
];
interface LogItem {
directory: string;
filename: string;
}
const language = navigator.language || navigator.languages[0];
const CronDetailModal = ({
cron = {},
handleCancel,
visible,
theme,
isPhone,
}: {
cron?: any;
visible: boolean;
handleCancel: (needUpdate?: boolean) => void;
theme: string;
isPhone: boolean;
}) => {
const [activeTabKey, setActiveTabKey] = useState('log');
const [loading, setLoading] = useState(true);
const [logs, setLogs] = useState<LogItem[]>([]);
const [log, setLog] = useState('');
const [value, setValue] = useState('');
const [isLogModalVisible, setIsLogModalVisible] = useState(false);
const editorRef = useRef<any>(null);
const [scriptInfo, setScriptInfo] = useState<any>({});
const [logUrl, setLogUrl] = useState('');
const [validTabs, setValidTabs] = useState(tabList);
const [currentCron, setCurrentCron] = useState<any>({});
const contentList: any = {
log: (
<List
dataSource={logs}
loading={loading}
renderItem={(item) => (
<List.Item className="log-item" onClick={() => onClickItem(item)}>
<FileOutlined style={{ marginRight: 10 }} />
{item.directory}/{item.filename}
</List.Item>
)}
/>
),
script: (
<Editor
language="shell"
theme={theme}
value={value}
options={{
fontSize: 12,
lineNumbersMinChars: 3,
fontFamily: 'Source Code Pro',
folding: false,
glyphMargin: false,
wordWrap: 'on',
}}
onMount={(editor) => {
editorRef.current = editor;
}}
/>
),
};
const onClickItem = (item: LogItem) => {
localStorage.setItem('logCron', currentCron.id);
setLogUrl(`${config.apiPrefix}logs/${item.directory}/${item.filename}`);
request
.get(`${config.apiPrefix}logs/${item.directory}/${item.filename}`)
.then((data) => {
setLog(data.data);
setIsLogModalVisible(true);
});
};
const onTabChange = (key: string) => {
setActiveTabKey(key);
};
const getLogs = () => {
setLoading(true);
request
.get(`${config.apiPrefix}crons/${cron.id}/logs`)
.then((data: any) => {
if (data.code === 200) {
setLogs(data.data);
}
})
.finally(() => setLoading(false));
};
const getScript = () => {
const cmd = cron.command.split(' ') as string[];
if (cmd[0] === 'task') {
setValidTabs(validTabs);
if (cmd[1].startsWith('/ql/data/scripts')) {
cmd[1] = cmd[1].replace('/ql/data/scripts/', '');
}
let [p, s] = cmd[1].split('/');
if (!s) {
s = p;
p = '';
}
setScriptInfo({ parent: p, filename: s });
request
.get(`${config.apiPrefix}scripts/${s}?path=${p || ''}`)
.then((data) => {
setValue(data.data);
});
} else {
setValidTabs([validTabs[0]]);
}
};
const saveFile = () => {
Modal.confirm({
title: `确认保存`,
content: (
<>
<Text style={{ wordBreak: 'break-all' }} type="warning">
{scriptInfo.filename}
</Text>{' '}
</>
),
onOk() {
const content = editorRef.current
? editorRef.current.getValue().replace(/\r\n/g, '\n')
: value;
return new Promise((resolve, reject) => {
request
.put(`${config.apiPrefix}scripts`, {
data: {
filename: scriptInfo.filename,
path: scriptInfo.parent || '',
content,
},
})
.then((_data: any) => {
if (_data.code === 200) {
setValue(content);
message.success(`保存成功`);
} else {
message.error(_data);
}
resolve(null);
})
.catch((e) => reject(e));
});
},
onCancel() {
console.log('Cancel');
},
});
};
const runCron = () => {
Modal.confirm({
title: '确认运行',
content: (
<>
{' '}
<Text style={{ wordBreak: 'break-all' }} type="warning">
{currentCron.name}
</Text>{' '}
</>
),
onOk() {
request
.put(`${config.apiPrefix}crons/run`, { data: [currentCron.id] })
.then((data: any) => {
if (data.code === 200) {
setCurrentCron({ ...currentCron, status: CrontabStatus.running });
setTimeout(() => {
getLogs();
}, 1000);
} else {
message.error(data);
}
});
},
onCancel() {
console.log('Cancel');
},
});
};
const stopCron = () => {
Modal.confirm({
title: '确认停止',
content: (
<>
{' '}
<Text style={{ wordBreak: 'break-all' }} type="warning">
{currentCron.name}
</Text>{' '}
</>
),
onOk() {
request
.put(`${config.apiPrefix}crons/stop`, { data: [currentCron.id] })
.then((data: any) => {
if (data.code === 200) {
setCurrentCron({ ...currentCron, status: CrontabStatus.idle });
} else {
message.error(data);
}
});
},
onCancel() {
console.log('Cancel');
},
});
};
const enabledOrDisabledCron = () => {
Modal.confirm({
title: `确认${currentCron.isDisabled === 1 ? '启用' : '禁用'}`,
content: (
<>
{currentCron.isDisabled === 1 ? '启用' : '禁用'}
{' '}
<Text style={{ wordBreak: 'break-all' }} type="warning">
{currentCron.name}
</Text>{' '}
</>
),
onOk() {
request
.put(
`${config.apiPrefix}crons/${
currentCron.isDisabled === 1 ? 'enable' : 'disable'
}`,
{
data: [currentCron.id],
},
)
.then((data: any) => {
if (data.code === 200) {
setCurrentCron({
...currentCron,
isDisabled: currentCron.isDisabled === 1 ? 0 : 1,
});
} else {
message.error(data);
}
});
},
onCancel() {
console.log('Cancel');
},
});
};
const pinOrUnPinCron = () => {
Modal.confirm({
title: `确认${currentCron.isPinned === 1 ? '取消置顶' : '置顶'}`,
content: (
<>
{currentCron.isPinned === 1 ? '取消置顶' : '置顶'}
{' '}
<Text style={{ wordBreak: 'break-all' }} type="warning">
{currentCron.name}
</Text>{' '}
</>
),
onOk() {
request
.put(
`${config.apiPrefix}crons/${
currentCron.isPinned === 1 ? 'unpin' : 'pin'
}`,
{
data: [currentCron.id],
},
)
.then((data: any) => {
if (data.code === 200) {
setCurrentCron({
...currentCron,
isPinned: currentCron.isPinned === 1 ? 0 : 1,
});
} else {
message.error(data);
}
});
},
onCancel() {
console.log('Cancel');
},
});
};
useEffect(() => {
if (cron && cron.id) {
setCurrentCron(cron);
getLogs();
getScript();
}
}, [cron]);
return (
<Modal
title={
<div className="crontab-title-wrapper">
<div>
<span>{currentCron.name}</span>
{currentCron.labels?.length > 0 && currentCron.labels[0] !== '' && (
<Divider type="vertical"></Divider>
)}
{currentCron.labels?.length > 0 &&
currentCron.labels[0] !== '' &&
currentCron.labels?.map((label: string, i: number) => (
<Tag color="blue" style={{ marginRight: 5 }}>
{label}
</Tag>
))}
</div>
<div className="operations">
<Tooltip
title={
currentCron.status === CrontabStatus.idle ? '运行' : '停止'
}
>
<Button
type="link"
icon={
currentCron.status === CrontabStatus.idle ? (
<PlayCircleOutlined />
) : (
<PauseCircleOutlined />
)
}
size="small"
onClick={
currentCron.status === CrontabStatus.idle ? runCron : stopCron
}
/>
</Tooltip>
<Tooltip title={currentCron.isDisabled === 1 ? '启用' : '禁用'}>
<Button
type="link"
icon={
<IconFont
type={
currentCron.isDisabled === 1
? 'ql-icon-enable'
: 'ql-icon-disable'
}
/>
}
size="small"
onClick={enabledOrDisabledCron}
/>
</Tooltip>
<Tooltip title={currentCron.isPinned === 1 ? '取消置顶' : '置顶'}>
<Button
type="link"
icon={
<IconFont
type={
currentCron.isPinned === 1
? 'ql-icon-untop'
: 'ql-icon-top'
}
/>
}
size="small"
onClick={pinOrUnPinCron}
/>
</Tooltip>
</div>
</div>
}
centered
visible={visible}
forceRender
footer={false}
onCancel={() => handleCancel()}
wrapClassName="crontab-detail"
width={!isPhone ? '80vw' : ''}
>
<div className="card-wrapper">
<Card>
<div className="cron-detail-info-item">
<div className="cron-detail-info-title"></div>
<div className="cron-detail-info-value">{currentCron.command}</div>
</div>
</Card>
<Card style={{ marginTop: 10 }}>
<div className="cron-detail-info-item">
<div className="cron-detail-info-title"></div>
<div className="cron-detail-info-value">
{(!currentCron.isDisabled ||
currentCron.status !== CrontabStatus.idle) && (
<>
{currentCron.status === CrontabStatus.idle && (
<Tag icon={<ClockCircleOutlined />} color="default">
</Tag>
)}
{currentCron.status === CrontabStatus.running && (
<Tag
icon={<Loading3QuartersOutlined spin />}
color="processing"
>
</Tag>
)}
{currentCron.status === CrontabStatus.queued && (
<Tag icon={<FieldTimeOutlined />} color="default">
</Tag>
)}
</>
)}
{currentCron.isDisabled === 1 &&
currentCron.status === CrontabStatus.idle && (
<Tag icon={<CloseCircleOutlined />} color="error">
</Tag>
)}
</div>
</div>
<div className="cron-detail-info-item">
<div className="cron-detail-info-title"></div>
<div className="cron-detail-info-value">{currentCron.schedule}</div>
</div>
<div className="cron-detail-info-item">
<div className="cron-detail-info-title"></div>
<div className="cron-detail-info-value">
{currentCron.last_execution_time
? new Date(currentCron.last_execution_time * 1000)
.toLocaleString(language, {
hour12: false,
})
.replace(' 24:', ' 00:')
: '-'}
</div>
</div>
<div className="cron-detail-info-item">
<div className="cron-detail-info-title"></div>
<div className="cron-detail-info-value">
{currentCron.last_running_time
? diffTime(currentCron.last_running_time)
: '-'}
</div>
</div>
<div className="cron-detail-info-item">
<div className="cron-detail-info-title"></div>
<div className="cron-detail-info-value">
{currentCron.nextRunTime &&
currentCron.nextRunTime
.toLocaleString(language, {
hour12: false,
})
.replace(' 24:', ' 00:')}
</div>
</div>
</Card>
<Card
style={{ marginTop: 10 }}
tabList={validTabs}
activeTabKey={activeTabKey}
onTabChange={(key) => {
onTabChange(key);
}}
tabBarExtraContent={
activeTabKey === 'script' && (
<Button
type="primary"
style={{ marginRight: 8 }}
onClick={saveFile}
>
</Button>
)
}
>
{contentList[activeTabKey]}
</Card>
</div>
<CronLogModal
visible={isLogModalVisible}
handleCancel={() => {
setIsLogModalVisible(false);
}}
cron={cron}
data={log}
logUrl={logUrl}
/>
</Modal>
);
};
export default CronDetailModal;