diff --git a/.gitignore b/.gitignore
index cb4233d3..94af1ace 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,7 @@
/node_modules
/npm-debug.log*
/yarn-error.log
+/yarn.lock
/pnpm-lock.yaml
/package-lock.json
diff --git a/.umirc.ts b/.umirc.ts
index 5222bea4..16204613 100644
--- a/.umirc.ts
+++ b/.umirc.ts
@@ -10,6 +10,7 @@ export default defineConfig({
},
fastRefresh: {},
esbuild: {},
+ webpack5: {},
dynamicImport: {
loading: '@/components/pageLoading',
},
diff --git a/back/api/cron.ts b/back/api/cron.ts
index 2c9f292a..da3c7a2f 100644
--- a/back/api/cron.ts
+++ b/back/api/cron.ts
@@ -333,4 +333,24 @@ export default (app: Router) => {
}
},
);
+
+ route.get(
+ '/:id/logs',
+ celebrate({
+ params: Joi.object({
+ id: Joi.number().required(),
+ }),
+ }),
+ async (req: Request, res: Response, next: NextFunction) => {
+ const logger: Logger = Container.get('logger');
+ try {
+ const cronService = Container.get(CronService);
+ const data = await cronService.logs(parseInt(req.params.id));
+ return res.send({ code: 200, data });
+ } catch (e) {
+ logger.error('🔥 error: %o', e);
+ return next(e);
+ }
+ },
+ );
};
diff --git a/back/services/cron.ts b/back/services/cron.ts
index 4c8d1a08..c78c349c 100644
--- a/back/services/cron.ts
+++ b/back/services/cron.ts
@@ -347,6 +347,40 @@ export default class CronService {
}
}
+ public async logs(id: number) {
+ const doc = await this.getDb({ id });
+ if (!doc) {
+ return [];
+ }
+
+ const [, commandStr, url] = doc.command.split(/ +/);
+ let logPath = this.getKey(commandStr);
+ const isQlCommand = doc.command.startsWith('ql ');
+ const key =
+ (url && ['repo', 'raw'].includes(commandStr) && this.getKey(url)) ||
+ logPath;
+ if (isQlCommand) {
+ logPath = 'update';
+ }
+ let logDir = `${config.logPath}${logPath}`;
+ if (existsSync(logDir)) {
+ let files = await promises.readdir(logDir);
+ console.log(files);
+ if (isQlCommand) {
+ files = files.filter((x) => x.includes(key));
+ }
+ return files
+ .map((x) => ({
+ filename: x,
+ directory: logPath,
+ time: fs.statSync(`${logDir}/${x}`).mtime.getTime(),
+ }))
+ .sort((a, b) => b.time - a.time);
+ } else {
+ return [];
+ }
+ }
+
private getKey(command: string) {
const start =
command.lastIndexOf('/') !== -1 ? command.lastIndexOf('/') + 1 : 0;
diff --git a/package.json b/package.json
index bccd7231..1ecf238c 100644
--- a/package.json
+++ b/package.json
@@ -26,16 +26,16 @@
},
"dependencies": {
"@otplib/preset-default": "^12.0.1",
- "@sentry/node": "^6.17.2",
- "@sentry/tracing": "^6.17.2",
- "body-parser": "^1.19.0",
- "celebrate": "^13.0.3",
- "chokidar": "^3.5.2",
+ "@sentry/node": "^6.18.1",
+ "@sentry/tracing": "^6.18.1",
+ "body-parser": "^1.19.2",
+ "celebrate": "^15.0.1",
+ "chokidar": "^3.5.3",
"cors": "^2.8.5",
- "cron-parser": "^3.5.0",
- "dotenv": "^8.2.0",
- "express": "^4.17.1",
- "express-jwt": "^6.0.0",
+ "cron-parser": "^4.2.1",
+ "dotenv": "^16.0.0",
+ "express": "^4.17.3",
+ "express-jwt": "^6.1.1",
"express-urlrewrite": "^1.4.0",
"got": "^11.8.2",
"hpagent": "^0.1.2",
@@ -43,55 +43,55 @@
"jsonwebtoken": "^8.5.1",
"lodash": "^4.17.21",
"nedb": "^1.8.0",
- "node-fetch": "^2.6.1",
- "node-schedule": "^2.0.0",
- "nodemailer": "^6.7.0",
- "p-queue": "6.6.2",
+ "node-fetch": "^3.2.1",
+ "node-schedule": "^2.1.0",
+ "nodemailer": "^6.7.2",
+ "p-queue": "7.2.0",
"reflect-metadata": "^0.1.13",
- "sequelize": "^7.0.0-alpha.3",
+ "sequelize": "^6.17.0",
"serve-handler": "^6.1.3",
- "sockjs": "^0.3.21",
+ "sockjs": "^0.3.24",
"sqlite3": "^5.0.2",
"toad-scheduler": "^1.6.0",
- "typedi": "^0.8.0",
+ "typedi": "^0.10.0",
"uuid": "^8.3.2",
- "winston": "^3.3.3",
- "yargs": "^17.2.1"
+ "winston": "^3.6.0",
+ "yargs": "^17.3.1"
},
"devDependencies": {
- "@ant-design/icons": "^4.6.2",
- "@ant-design/pro-layout": "^6.26.0",
+ "@ant-design/icons": "^4.7.0",
+ "@ant-design/pro-layout": "^6.33.1",
"@monaco-editor/react": "^4.3.1",
- "@sentry/react": "^6.17.2",
- "@types/cors": "^2.8.10",
- "@types/express": "^4.17.8",
- "@types/express-jwt": "^6.0.1",
- "@types/jsonwebtoken": "^8.5.0",
- "@types/lodash": "^4.14.170",
+ "@sentry/react": "^6.18.1",
+ "@types/cors": "^2.8.12",
+ "@types/express": "^4.17.13",
+ "@types/express-jwt": "^6.0.4",
+ "@types/jsonwebtoken": "^8.5.8",
+ "@types/lodash": "^4.14.179",
"@types/nedb": "^1.8.12",
- "@types/node": "^14.11.2",
- "@types/node-fetch": "^2.5.8",
+ "@types/node": "^17.0.21",
+ "@types/node-fetch": "^2.6.1",
"@types/node-schedule": "^1.3.2",
"@types/nodemailer": "^6.4.4",
- "@types/qrcode.react": "^1.0.1",
- "@types/react": "^17.0.0",
- "@types/react-dom": "^17.0.0",
+ "@types/qrcode.react": "^1.0.2",
+ "@types/react": "^17.0.39",
+ "@types/react-dom": "^17.0.13",
"@types/serve-handler": "^6.1.1",
"@types/sockjs": "^0.3.33",
"@types/sockjs-client": "^1.5.1",
- "@types/uuid": "^8.3.3",
- "@umijs/plugin-antd": "^0.11.0",
+ "@types/uuid": "^8.3.4",
+ "@umijs/plugin-antd": "^0.15.0",
"@umijs/plugin-esbuild": "^1.4.1",
- "@umijs/test": "^3.3.9",
+ "@umijs/test": "^3.5.21",
"ansi-to-react": "^6.1.6",
- "antd": "^4.17.0-alpha.6",
- "codemirror": "^5.62.2",
- "compression-webpack-plugin": "6.1.1",
+ "antd": "^4.18.9",
+ "codemirror": "^5.65.2",
+ "compression-webpack-plugin": "9.2.0",
"concurrently": "^7.0.0",
- "darkreader": "4.9.40",
- "lint-staged": "^10.0.7",
- "nodemon": "^2.0.4",
- "prettier": "^2.2.0",
+ "darkreader": "4.9.44",
+ "lint-staged": "^12.3.4",
+ "nodemon": "^2.0.15",
+ "prettier": "^2.5.1",
"qiniu": "^7.4.0",
"qrcode.react": "^1.0.1",
"react": "17.x",
@@ -101,13 +101,13 @@
"react-dnd-html5-backend": "^14.0.0",
"react-dom": "17.x",
"react-split-pane": "^0.1.92",
- "sockjs-client": "^1.5.2",
- "ts-node": "^9.0.0",
- "typescript": "^4.1.2",
- "umi": "^3.5.0",
- "umi-request": "^1.3.5",
+ "sockjs-client": "^1.6.0",
+ "ts-node": "^10.6.0",
+ "typescript": "^4.6.2",
+ "umi": "^3.5.21",
+ "umi-request": "^1.4.0",
"vh-check": "^2.0.5",
- "webpack": "^5.28.0",
+ "webpack": "^5.70.0",
"yorkie": "^2.0.0"
}
}
diff --git a/src/pages/crontab/detail.tsx b/src/pages/crontab/detail.tsx
index c99e30d8..151280b9 100644
--- a/src/pages/crontab/detail.tsx
+++ b/src/pages/crontab/detail.tsx
@@ -7,7 +7,7 @@ import {
Button,
Card,
Tag,
- Popover,
+ List,
Divider,
} from 'antd';
import {
@@ -15,14 +15,14 @@ import {
CloseCircleOutlined,
FieldTimeOutlined,
Loading3QuartersOutlined,
+ FileOutlined,
} from '@ant-design/icons';
import { CrontabStatus } from './index';
import { diffTime } from '@/utils/date';
-
-const contentList: any = {
- log:
log content
,
- script: script content
,
-};
+import { request } from '@/utils/http';
+import config from '@/utils/config';
+import CronLogModal from './logModal';
+import Editor from '@monaco-editor/react';
const tabList = [
{
@@ -35,23 +35,114 @@ const tabList = [
},
];
+interface LogItem {
+ directory: string;
+ filename: string;
+}
+
const language = navigator.language || navigator.languages[0];
const CronDetailModal = ({
cron = {},
handleCancel,
visible,
+ theme,
}: {
cron?: any;
visible: boolean;
handleCancel: (needUpdate?: boolean) => void;
+ theme: string;
}) => {
const [activeTabKey, setActiveTabKey] = useState('log');
+ const [loading, setLoading] = useState(true);
+ const [logs, setLogs] = useState([]);
+ const [log, setLog] = useState('');
+ const [value, setValue] = useState('');
+ const [isLogModalVisible, setIsLogModalVisible] = useState(false);
+
+ const contentList: any = {
+ log: (
+ (
+ onClickItem(item)}>
+
+ {item.directory}/{item.filename}
+
+ )}
+ />
+ ),
+ script: (
+
+ ),
+ };
+
+ const onClickItem = (item: LogItem) => {
+ 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') {
+ 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 = '';
+ }
+ request
+ .get(`${config.apiPrefix}scripts/${s}?path=${p || ''}`)
+ .then((data) => {
+ setValue(data.data);
+ });
+ }
+ };
+
+ useEffect(() => {
+ if (cron && cron.id) {
+ getLogs();
+ getScript();
+ }
+ }, [cron]);
+
return (
-
+
状态
@@ -158,6 +249,14 @@ const CronDetailModal = ({
{contentList[activeTabKey]}
+ {
+ setIsLogModalVisible(false);
+ }}
+ cron={cron}
+ data={log}
+ />
);
};
diff --git a/src/pages/crontab/index.less b/src/pages/crontab/index.less
index 6405f1ad..b127061f 100644
--- a/src/pages/crontab/index.less
+++ b/src/pages/crontab/index.less
@@ -19,3 +19,10 @@
margin-top: 18px;
}
}
+
+.log-item {
+ cursor: pointer;
+ &:hover {
+ background: #fafafa;
+ }
+}
diff --git a/src/pages/crontab/index.tsx b/src/pages/crontab/index.tsx
index 3b546297..03b43ce4 100644
--- a/src/pages/crontab/index.tsx
+++ b/src/pages/crontab/index.tsx
@@ -70,7 +70,7 @@ enum OperationPath {
'unpin',
}
-const Crontab = ({ headerStyle, isPhone }: any) => {
+const Crontab = ({ headerStyle, isPhone, theme }: any) => {
const columns: any = [
{
title: '任务名',
@@ -90,14 +90,16 @@ const Crontab = ({ headerStyle, isPhone }: any) => {
trigger={isPhone ? 'click' : 'hover'}
content={
- {record.labels?.map((label: string, i: number) => (
+ {record.labels?.map((label: string) => (
{
- onSearch(`label:${label}`);
+ style={{ cursor: 'point' }}
+ onClick={(e) => {
+ e.stopPropagation();
+ setSearchText(`label:${label}`);
}}
>
- {label}
+ {label}
))}
@@ -838,7 +840,9 @@ const Crontab = ({ headerStyle, isPhone }: any) => {
placeholder="请输入名称或者关键词"
style={{ width: 'auto' }}
enterButton
+ allowClear
loading={loading}
+ value={searchText}
onSearch={onSearch}
/>,