mirror of
https://github.com/whyour/qinglong.git
synced 2026-06-30 20:35:09 +08:00
定时任务显示完整运行历史
This commit is contained in:
+1
-1
@@ -434,6 +434,7 @@ export default (app: Router) => {
|
||||
log_path: Joi.string().optional().allow(null),
|
||||
last_running_time: Joi.number().optional().allow(null),
|
||||
last_execution_time: Joi.number().optional().allow(null),
|
||||
exit_code: Joi.number().optional().allow(null),
|
||||
}),
|
||||
}),
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
@@ -463,7 +464,6 @@ export default (app: Router) => {
|
||||
const instances = await RunningInstanceModel.findAll({
|
||||
where: {
|
||||
cron_id: req.params.id,
|
||||
status: InstanceStatus.running,
|
||||
},
|
||||
order: [['started_at', 'DESC']],
|
||||
raw: true,
|
||||
|
||||
+11
-1
@@ -11,6 +11,7 @@ import {
|
||||
rmPath,
|
||||
} from '../config/util';
|
||||
import LogService from '../services/log';
|
||||
import { InstanceStatus, RunningInstanceModel } from '../data/runningInstance';
|
||||
const route = Router();
|
||||
const blacklist = ['.tmp'];
|
||||
|
||||
@@ -46,8 +47,17 @@ export default (app: Router) => {
|
||||
message: t('暂无权限'),
|
||||
});
|
||||
}
|
||||
const logPath = `${req.query.path as string}/${req.query.file as string}`;
|
||||
const runningInstance = await RunningInstanceModel.findOne({
|
||||
where: { log_path: logPath, status: InstanceStatus.running },
|
||||
});
|
||||
|
||||
const content = await getFileContentByName(finalPath);
|
||||
res.send({ code: 200, data: removeAnsi(content) });
|
||||
res.send({
|
||||
code: 200,
|
||||
data: removeAnsi(content),
|
||||
logStatus: runningInstance ? 'running' : undefined,
|
||||
});
|
||||
} catch (e) {
|
||||
return next(e);
|
||||
}
|
||||
|
||||
+9
-31
@@ -166,6 +166,7 @@ export default class CronService {
|
||||
log_path,
|
||||
last_running_time = 0,
|
||||
last_execution_time = 0,
|
||||
exit_code,
|
||||
}: {
|
||||
ids: number[];
|
||||
status: CrontabStatus;
|
||||
@@ -173,6 +174,7 @@ export default class CronService {
|
||||
log_path: string;
|
||||
last_running_time: number;
|
||||
last_execution_time: number;
|
||||
exit_code?: number;
|
||||
}) {
|
||||
let options: any = {
|
||||
status,
|
||||
@@ -209,10 +211,15 @@ export default class CronService {
|
||||
} else if (status === CrontabStatus.idle) {
|
||||
// Mark the matching running instance as finished
|
||||
const finishedAt = dayjs().unix();
|
||||
const instanceStatus =
|
||||
exit_code !== undefined && exit_code !== null && exit_code !== 0
|
||||
? InstanceStatus.error
|
||||
: InstanceStatus.finished;
|
||||
await RunningInstanceModel.update(
|
||||
{
|
||||
finished_at: finishedAt,
|
||||
status: InstanceStatus.finished,
|
||||
status: instanceStatus,
|
||||
exit_code: exit_code ?? undefined,
|
||||
},
|
||||
{
|
||||
where: {
|
||||
@@ -564,7 +571,7 @@ export default class CronService {
|
||||
}
|
||||
}
|
||||
await RunningInstanceModel.update(
|
||||
{ status: InstanceStatus.stopped, finished_at: dayjs().unix() },
|
||||
{ status: InstanceStatus.stopped, finished_at: dayjs().unix(), exit_code: 143 },
|
||||
{ where: { id: instanceId } },
|
||||
);
|
||||
|
||||
@@ -619,15 +626,6 @@ export default class CronService {
|
||||
{ shell: '/bin/bash' },
|
||||
);
|
||||
|
||||
const startedAt = dayjs().unix();
|
||||
const instance = await RunningInstanceModel.create({
|
||||
cron_id: id!,
|
||||
pid: cp.pid,
|
||||
log_path: logPath,
|
||||
started_at: startedAt,
|
||||
status: InstanceStatus.running,
|
||||
});
|
||||
|
||||
await CrontabModel.update(
|
||||
{ status: CrontabStatus.running, pid: cp.pid, log_path: logPath },
|
||||
{ where: { id } },
|
||||
@@ -659,26 +657,6 @@ export default class CronService {
|
||||
code,
|
||||
);
|
||||
await logStreamManager.closeStream(absolutePath);
|
||||
const finishedAt = dayjs().unix();
|
||||
await RunningInstanceModel.update(
|
||||
{
|
||||
finished_at: finishedAt,
|
||||
status: code === 0 ? InstanceStatus.finished : InstanceStatus.error,
|
||||
exit_code: code ?? undefined,
|
||||
},
|
||||
{ where: { id: instance.id } },
|
||||
);
|
||||
|
||||
// Only set cron to idle if no other running instances exist
|
||||
const otherRunning = await RunningInstanceModel.count({
|
||||
where: { cron_id: id!, status: InstanceStatus.running },
|
||||
});
|
||||
if (otherRunning === 0) {
|
||||
await CrontabModel.update(
|
||||
{ status: CrontabStatus.idle, pid: undefined },
|
||||
{ where: { id } },
|
||||
);
|
||||
}
|
||||
resolve({ ...params, pid: cp.pid, code });
|
||||
});
|
||||
});
|
||||
|
||||
+7
-1
@@ -141,13 +141,19 @@ update_cron() {
|
||||
local logPath="$4"
|
||||
local lastExecutingTime="${5:-0}"
|
||||
local runningTime="${6:-0}"
|
||||
local exitCode="${7:-}"
|
||||
local currentTimeStamp=$(date +%s)
|
||||
local dataRaw="{\"ids\":[$ids],\"status\":\"$status\",\"pid\":\"$pid\",\"log_path\":\"$logPath\",\"last_execution_time\":$lastExecutingTime,\"last_running_time\":$runningTime"
|
||||
if [[ -n $exitCode ]]; then
|
||||
dataRaw="${dataRaw},\"exit_code\":$exitCode"
|
||||
fi
|
||||
dataRaw="${dataRaw}}"
|
||||
local api=$(
|
||||
curl -s --noproxy "*" "http://localhost:${ql_port}/open/crons/status?t=$currentTimeStamp" \
|
||||
-X 'PUT' \
|
||||
-H "Authorization: Bearer ${__ql_token__}" \
|
||||
-H "Content-Type: application/json;charset=UTF-8" \
|
||||
--data-raw "{\"ids\":[$ids],\"status\":\"$status\",\"pid\":\"$pid\",\"log_path\":\"$logPath\",\"last_execution_time\":$lastExecutingTime,\"last_running_time\":$runningTime}" \
|
||||
--data-raw "$dataRaw" \
|
||||
--compressed
|
||||
)
|
||||
code=$(echo "$api" | jq -r .code)
|
||||
|
||||
+1
-1
@@ -414,7 +414,7 @@ handle_task_end() {
|
||||
[[ "$diff_time" == 0 ]] && diff_time=1
|
||||
|
||||
if [[ $ID ]]; then
|
||||
local error=$(update_cron "\"$ID\"" "1" "$$" "$log_path" "$begin_timestamp" "$diff_time")
|
||||
local error=$(update_cron "\"$ID\"" "1" "$$" "$log_path" "$begin_timestamp" "$diff_time" "$exit_code")
|
||||
if [[ $error ]]; then
|
||||
error_message=", 状态更新失败(${error})"
|
||||
fi
|
||||
|
||||
@@ -600,5 +600,10 @@
|
||||
"确认保存": "Confirm to save",
|
||||
"上传失败": "Upload failed",
|
||||
"成功上传": "Successfully uploaded",
|
||||
"个环境变量": " environment variables"
|
||||
"个环境变量": " environment variables",
|
||||
"运行历史": "Run History",
|
||||
"已完成": "Completed",
|
||||
"已停止": "Stopped",
|
||||
"退出码": "Exit Code",
|
||||
"结束": "End"
|
||||
}
|
||||
|
||||
@@ -600,5 +600,10 @@
|
||||
"确认保存": "确认保存",
|
||||
"上传失败": "上传失败",
|
||||
"成功上传": "成功上传",
|
||||
"个环境变量": "个环境变量"
|
||||
"个环境变量": "个环境变量",
|
||||
"运行历史": "运行历史",
|
||||
"已完成": "已完成",
|
||||
"已停止": "已停止",
|
||||
"退出码": "退出码",
|
||||
"结束": "结束"
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@ import React, { useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
Modal,
|
||||
message,
|
||||
Input,
|
||||
Form,
|
||||
Button,
|
||||
Card,
|
||||
Tag,
|
||||
@@ -40,24 +38,15 @@ const { Text } = Typography;
|
||||
|
||||
const tabList = [
|
||||
{
|
||||
key: 'log',
|
||||
tab: intl.get('日志'),
|
||||
key: 'runningHistory',
|
||||
tab: intl.get('运行历史'),
|
||||
},
|
||||
{
|
||||
key: 'script',
|
||||
tab: intl.get('脚本'),
|
||||
},
|
||||
{
|
||||
key: 'runningInstance',
|
||||
tab: intl.get('运行实例'),
|
||||
},
|
||||
];
|
||||
|
||||
interface LogItem {
|
||||
directory: string;
|
||||
filename: string;
|
||||
}
|
||||
|
||||
const CronDetailModal = ({
|
||||
cron = {},
|
||||
handleCancel,
|
||||
@@ -69,21 +58,19 @@ const CronDetailModal = ({
|
||||
theme: string;
|
||||
isPhone: boolean;
|
||||
}) => {
|
||||
const [activeTabKey, setActiveTabKey] = useState('log');
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [logs, setLogs] = useState<LogItem[]>([]);
|
||||
const [log, setLog] = useState('');
|
||||
const [activeTabKey, setActiveTabKey] = useState('runningHistory');
|
||||
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 listRef = useRef<HTMLDivElement>(null);
|
||||
const tableScrollHeight = useScrollHeight(listRef);
|
||||
const [runningInstances, setRunningInstances] = useState<any[]>([]);
|
||||
const needRefreshRef = useRef(false);
|
||||
const [isLogModalVisible, setIsLogModalVisible] = useState(false);
|
||||
const [logData, setLogData] = useState('');
|
||||
const [logUrl, setLogUrl] = useState('');
|
||||
|
||||
const fetchRunningInstances = async () => {
|
||||
if (!cron.id) return Promise.resolve();
|
||||
@@ -114,25 +101,6 @@ const CronDetailModal = ({
|
||||
}, [cron.id]);
|
||||
|
||||
const contentList: any = {
|
||||
log: (
|
||||
<div ref={listRef}>
|
||||
<List>
|
||||
<VirtualList
|
||||
data={logs}
|
||||
height={tableScrollHeight}
|
||||
itemHeight={47}
|
||||
itemKey="filename"
|
||||
>
|
||||
{(item) => (
|
||||
<List.Item className="log-item" onClick={() => onClickItem(item)}>
|
||||
<FileOutlined style={{ marginRight: 10 }} />
|
||||
{item.directory}/{item.filename}
|
||||
</List.Item>
|
||||
)}
|
||||
</VirtualList>
|
||||
</List>
|
||||
</div>
|
||||
),
|
||||
script: scriptInfo.filename && (
|
||||
<Editor
|
||||
language={getEditorMode(scriptInfo.filename)}
|
||||
@@ -150,7 +118,7 @@ const CronDetailModal = ({
|
||||
}}
|
||||
/>
|
||||
),
|
||||
runningInstance: (
|
||||
runningHistory: (
|
||||
<div ref={listRef}>
|
||||
<List>
|
||||
<VirtualList
|
||||
@@ -163,40 +131,77 @@ const CronDetailModal = ({
|
||||
<List.Item
|
||||
className="log-item"
|
||||
actions={[
|
||||
<Tooltip title={intl.get('查看日志')} key="log">
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
icon={<FileOutlined />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
viewInstanceLog(item);
|
||||
}}
|
||||
/>
|
||||
</Tooltip>,
|
||||
<Tooltip title={intl.get('停止')} key="stop">
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
icon={<StopOutlined />}
|
||||
danger
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
stopRunningInstance(item);
|
||||
}}
|
||||
/>
|
||||
</Tooltip>,
|
||||
]}
|
||||
item.log_path && (
|
||||
<Tooltip title={intl.get('查看日志')} key="log">
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
icon={<FileOutlined />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
viewInstanceLog(item);
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
),
|
||||
item.status === 0 && (
|
||||
<Tooltip title={intl.get('停止')} key="stop">
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
icon={<StopOutlined />}
|
||||
danger
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
stopRunningInstance(item);
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
),
|
||||
].filter(Boolean)}
|
||||
>
|
||||
<List.Item.Meta
|
||||
title={
|
||||
<span>
|
||||
PID: <Tag color="processing">{item.pid}</Tag>
|
||||
{item.log_path && (
|
||||
<span style={{ marginRight: 8 }}>
|
||||
{item.log_path.split('/').pop()}
|
||||
</span>
|
||||
)}
|
||||
{item.status === 0 && (
|
||||
<Tag icon={<Loading3QuartersOutlined spin />} color="processing">
|
||||
{intl.get('运行中')}
|
||||
</Tag>
|
||||
)}
|
||||
{item.status === 1 && (
|
||||
<Tag color="success">{intl.get('已完成')}</Tag>
|
||||
)}
|
||||
{item.status === 2 && (
|
||||
<Tag color="default">{intl.get('已停止')}</Tag>
|
||||
)}
|
||||
{item.status === 3 && (
|
||||
<Tag color="error">{intl.get('错误')}</Tag>
|
||||
)}
|
||||
</span>
|
||||
}
|
||||
description={
|
||||
<span style={{ color: '#999' }}>
|
||||
{item.status === 0 && item.pid && (
|
||||
<span>
|
||||
PID: {item.pid}{' | '}
|
||||
</span>
|
||||
)}
|
||||
{intl.get('启动')}: {dayjs.unix(item.started_at).format('YYYY-MM-DD HH:mm:ss')}
|
||||
{item.finished_at && (
|
||||
<span>
|
||||
{' | '}{intl.get('结束')}: {dayjs.unix(item.finished_at).format('YYYY-MM-DD HH:mm:ss')}
|
||||
</span>
|
||||
)}
|
||||
{item.exit_code !== undefined && item.exit_code !== null && (
|
||||
<span>
|
||||
{' | '}{intl.get('退出码')}: {item.exit_code}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
@@ -225,7 +230,6 @@ const CronDetailModal = ({
|
||||
message.success(intl.get('实例已停止'));
|
||||
needRefreshRef.current = true;
|
||||
fetchRunningInstances();
|
||||
setTimeout(() => getLogs(), 1000);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -242,20 +246,7 @@ const CronDetailModal = ({
|
||||
setLogUrl(url);
|
||||
request.get(url).then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setLog(data);
|
||||
setIsLogModalVisible(true);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const onClickItem = (item: LogItem) => {
|
||||
const url = `${config.apiPrefix}logs/detail?file=${item.filename}&path=${item.directory || ''
|
||||
}`;
|
||||
localStorage.setItem('logCron', url);
|
||||
setLogUrl(url);
|
||||
request.get(url).then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setLog(data);
|
||||
setLogData(data);
|
||||
setIsLogModalVisible(true);
|
||||
}
|
||||
});
|
||||
@@ -265,22 +256,9 @@ const CronDetailModal = ({
|
||||
setActiveTabKey(key);
|
||||
};
|
||||
|
||||
const getLogs = () => {
|
||||
setLoading(true);
|
||||
request
|
||||
.get(`${config.apiPrefix}crons/${cron.id}/logs`)
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setLogs(data);
|
||||
}
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
|
||||
const getScript = () => {
|
||||
const result = getCommandScript(cron.command);
|
||||
if (Array.isArray(result)) {
|
||||
setValidTabs(validTabs);
|
||||
const [s, p] = result;
|
||||
setScriptInfo({ parent: p, filename: s });
|
||||
request
|
||||
@@ -291,8 +269,8 @@ const CronDetailModal = ({
|
||||
}
|
||||
});
|
||||
} else {
|
||||
setValidTabs([validTabs[0]]);
|
||||
setActiveTabKey('log');
|
||||
setValidTabs([{ key: 'runningHistory', tab: intl.get('运行历史') }]);
|
||||
setActiveTabKey('runningHistory');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -351,9 +329,7 @@ const CronDetailModal = ({
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setCurrentCron({ ...currentCron, status: CrontabStatus.running });
|
||||
setTimeout(() => {
|
||||
getLogs();
|
||||
}, 1000);
|
||||
fetchRunningInstances();
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -460,7 +436,6 @@ const CronDetailModal = ({
|
||||
useEffect(() => {
|
||||
if (cron && cron.id) {
|
||||
setCurrentCron(cron);
|
||||
getLogs();
|
||||
getScript();
|
||||
}
|
||||
}, [cron]);
|
||||
@@ -687,9 +662,10 @@ const CronDetailModal = ({
|
||||
<CronLogModal
|
||||
handleCancel={() => {
|
||||
setIsLogModalVisible(false);
|
||||
fetchRunningInstances();
|
||||
}}
|
||||
cron={cron}
|
||||
data={log}
|
||||
data={logData}
|
||||
logUrl={logUrl}
|
||||
/>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user