From d3016431ce376def56bd4c8896635ead4781e0ac Mon Sep 17 00:00:00 2001 From: whyour Date: Sun, 28 Jun 2026 15:48:58 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9A=E6=97=B6=E4=BB=BB=E5=8A=A1=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E5=AE=8C=E6=95=B4=E8=BF=90=E8=A1=8C=E5=8E=86=E5=8F=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- back/api/cron.ts | 2 +- back/api/log.ts | 12 ++- back/services/cron.ts | 40 ++------ shell/api.sh | 8 +- shell/share.sh | 2 +- src/locales/en-US.json | 7 +- src/locales/zh-CN.json | 7 +- src/pages/crontab/detail.tsx | 174 +++++++++++++++-------------------- 8 files changed, 116 insertions(+), 136 deletions(-) diff --git a/back/api/cron.ts b/back/api/cron.ts index 6be6f4a3..0067a0c8 100644 --- a/back/api/cron.ts +++ b/back/api/cron.ts @@ -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, diff --git a/back/api/log.ts b/back/api/log.ts index 0c1d03e8..d642d510 100644 --- a/back/api/log.ts +++ b/back/api/log.ts @@ -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); } diff --git a/back/services/cron.ts b/back/services/cron.ts index 16e24693..2638ac94 100644 --- a/back/services/cron.ts +++ b/back/services/cron.ts @@ -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 }); }); }); diff --git a/shell/api.sh b/shell/api.sh index b7366cb8..59f3b5f9 100755 --- a/shell/api.sh +++ b/shell/api.sh @@ -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) diff --git a/shell/share.sh b/shell/share.sh index 027f65d8..f182e4b7 100755 --- a/shell/share.sh +++ b/shell/share.sh @@ -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 diff --git a/src/locales/en-US.json b/src/locales/en-US.json index 30ec0dd5..476b433f 100644 --- a/src/locales/en-US.json +++ b/src/locales/en-US.json @@ -600,5 +600,10 @@ "确认保存": "Confirm to save", "上传失败": "Upload failed", "成功上传": "Successfully uploaded", - "个环境变量": " environment variables" + "个环境变量": " environment variables", + "运行历史": "Run History", + "已完成": "Completed", + "已停止": "Stopped", + "退出码": "Exit Code", + "结束": "End" } diff --git a/src/locales/zh-CN.json b/src/locales/zh-CN.json index 3c6deddb..9f844f48 100644 --- a/src/locales/zh-CN.json +++ b/src/locales/zh-CN.json @@ -600,5 +600,10 @@ "确认保存": "确认保存", "上传失败": "上传失败", "成功上传": "成功上传", - "个环境变量": "个环境变量" + "个环境变量": "个环境变量", + "运行历史": "运行历史", + "已完成": "已完成", + "已停止": "已停止", + "退出码": "退出码", + "结束": "结束" } diff --git a/src/pages/crontab/detail.tsx b/src/pages/crontab/detail.tsx index 031d0ddb..172c0872 100644 --- a/src/pages/crontab/detail.tsx +++ b/src/pages/crontab/detail.tsx @@ -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([]); - const [log, setLog] = useState(''); + const [activeTabKey, setActiveTabKey] = useState('runningHistory'); const [value, setValue] = useState(''); - const [isLogModalVisible, setIsLogModalVisible] = useState(false); const editorRef = useRef(null); const [scriptInfo, setScriptInfo] = useState({}); - const [logUrl, setLogUrl] = useState(''); const [validTabs, setValidTabs] = useState(tabList); const [currentCron, setCurrentCron] = useState({}); const listRef = useRef(null); const tableScrollHeight = useScrollHeight(listRef); const [runningInstances, setRunningInstances] = useState([]); 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: ( -
- - - {(item) => ( - onClickItem(item)}> - - {item.directory}/{item.filename} - - )} - - -
- ), script: scriptInfo.filename && ( ), - runningInstance: ( + runningHistory: (
-