From 63c80fe64878377b43cef4d454b77ed492f9c023 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 8 Nov 2025 17:14:15 +0000 Subject: [PATCH 1/5] Initial plan From 6b4ca7929332075f59b845a6e5cab9059b1e22e5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 8 Nov 2025 17:23:59 +0000 Subject: [PATCH 2/5] Add log_name field to enable custom log folder naming Co-authored-by: whyour <22700758+whyour@users.noreply.github.com> --- back/data/cron.ts | 3 +++ back/services/cron.ts | 4 ++-- back/validation/schedule.ts | 1 + src/locales/en-US.json | 5 ++++- src/locales/zh-CN.json | 5 ++++- src/pages/crontab/modal.tsx | 9 +++++++++ 6 files changed, 23 insertions(+), 4 deletions(-) diff --git a/back/data/cron.ts b/back/data/cron.ts index b234d59c..c75707a3 100644 --- a/back/data/cron.ts +++ b/back/data/cron.ts @@ -21,6 +21,7 @@ export class Crontab { extra_schedules?: Array<{ schedule: string }>; task_before?: string; task_after?: string; + log_name?: string; constructor(options: Crontab) { this.name = options.name; @@ -45,6 +46,7 @@ export class Crontab { this.extra_schedules = options.extra_schedules; this.task_before = options.task_before; this.task_after = options.task_after; + this.log_name = options.log_name; } } @@ -84,4 +86,5 @@ export const CrontabModel = sequelize.define('Crontab', { extra_schedules: DataTypes.JSON, task_before: DataTypes.STRING, task_after: DataTypes.STRING, + log_name: DataTypes.STRING, }); diff --git a/back/services/cron.ts b/back/services/cron.ts index a838ce9e..380428a3 100644 --- a/back/services/cron.ts +++ b/back/services/cron.ts @@ -476,8 +476,8 @@ export default class CronService { `[panel][开始执行任务] 参数: ${JSON.stringify(params)}`, ); - let { id, command, log_path } = cron; - const uniqPath = await getUniqPath(command, `${id}`); + let { id, command, log_path, log_name } = cron; + const uniqPath = log_name || (await getUniqPath(command, `${id}`)); const logTime = dayjs().format('YYYY-MM-DD-HH-mm-ss-SSS'); const logDirPath = path.resolve(config.logPath, `${uniqPath}`); if (log_path?.split('/')?.every((x) => x !== uniqPath)) { diff --git a/back/validation/schedule.ts b/back/validation/schedule.ts index a280156a..f8a503b8 100644 --- a/back/validation/schedule.ts +++ b/back/validation/schedule.ts @@ -37,4 +37,5 @@ export const commonCronSchema = { extra_schedules: Joi.array().optional().allow(null), task_before: Joi.string().optional().allow('').allow(null), task_after: Joi.string().optional().allow('').allow(null), + log_name: Joi.string().optional().allow('').allow(null), }; diff --git a/src/locales/en-US.json b/src/locales/en-US.json index 9a21ef3a..9b7d1ad7 100644 --- a/src/locales/en-US.json +++ b/src/locales/en-US.json @@ -521,5 +521,8 @@ "远程仓库缓存": "Remote repository cache", "SSH 文件缓存": "SSH file cache", "清除依赖缓存": "Clean dependency cache", - "清除成功": "Clean successful" + "清除成功": "Clean successful", + "日志名称": "Log Name", + "自定义日志文件夹名称,用于区分不同任务的日志,留空则自动生成": "Custom log folder name to distinguish logs from different tasks. Leave blank to auto-generate", + "请输入自定义日志文件夹名称": "Please enter a custom log folder name" } diff --git a/src/locales/zh-CN.json b/src/locales/zh-CN.json index c19d6984..42788752 100644 --- a/src/locales/zh-CN.json +++ b/src/locales/zh-CN.json @@ -521,5 +521,8 @@ "远程仓库缓存": "远程仓库缓存", "SSH 文件缓存": "SSH 文件缓存", "清除依赖缓存": "清除依赖缓存", - "清除成功": "清除成功" + "清除成功": "清除成功", + "日志名称": "日志名称", + "自定义日志文件夹名称,用于区分不同任务的日志,留空则自动生成": "自定义日志文件夹名称,用于区分不同任务的日志,留空则自动生成", + "请输入自定义日志文件夹名称": "请输入自定义日志文件夹名称" } diff --git a/src/pages/crontab/modal.tsx b/src/pages/crontab/modal.tsx index 6a7c0492..7f87f427 100644 --- a/src/pages/crontab/modal.tsx +++ b/src/pages/crontab/modal.tsx @@ -180,6 +180,15 @@ const CronModal = ({ + + + Date: Sat, 8 Nov 2025 17:25:57 +0000 Subject: [PATCH 3/5] Add database migration for log_name column Co-authored-by: whyour <22700758+whyour@users.noreply.github.com> --- back/loaders/db.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/back/loaders/db.ts b/back/loaders/db.ts index ac142a2d..8dd00ca8 100644 --- a/back/loaders/db.ts +++ b/back/loaders/db.ts @@ -56,6 +56,11 @@ export default async () => { try { await sequelize.query('alter table Crontabs add column task_after TEXT'); } catch (error) {} + try { + await sequelize.query( + 'alter table Crontabs add column log_name VARCHAR(255)', + ); + } catch (error) {} Logger.info('✌️ DB loaded'); } catch (error) { From 75d7ad4e026bd73d0546d45b3d23c0a9450c050b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 8 Nov 2025 17:34:45 +0000 Subject: [PATCH 4/5] Add security validation to prevent path traversal attacks Co-authored-by: whyour <22700758+whyour@users.noreply.github.com> --- back/services/cron.ts | 6 +++++- back/validation/schedule.ts | 11 ++++++++++- src/locales/en-US.json | 4 +++- src/locales/zh-CN.json | 4 +++- src/pages/crontab/modal.tsx | 15 ++++++++++++++- 5 files changed, 35 insertions(+), 5 deletions(-) diff --git a/back/services/cron.ts b/back/services/cron.ts index 380428a3..751e49ce 100644 --- a/back/services/cron.ts +++ b/back/services/cron.ts @@ -477,7 +477,11 @@ export default class CronService { ); let { id, command, log_path, log_name } = cron; - const uniqPath = log_name || (await getUniqPath(command, `${id}`)); + // Sanitize log_name to prevent path traversal + const sanitizedLogName = log_name + ? log_name.replace(/[\/\\\.]/g, '_').replace(/^_+|_+$/g, '') + : ''; + const uniqPath = sanitizedLogName || (await getUniqPath(command, `${id}`)); const logTime = dayjs().format('YYYY-MM-DD-HH-mm-ss-SSS'); const logDirPath = path.resolve(config.logPath, `${uniqPath}`); if (log_path?.split('/')?.every((x) => x !== uniqPath)) { diff --git a/back/validation/schedule.ts b/back/validation/schedule.ts index f8a503b8..56e15f84 100644 --- a/back/validation/schedule.ts +++ b/back/validation/schedule.ts @@ -37,5 +37,14 @@ export const commonCronSchema = { extra_schedules: Joi.array().optional().allow(null), task_before: Joi.string().optional().allow('').allow(null), task_after: Joi.string().optional().allow('').allow(null), - log_name: Joi.string().optional().allow('').allow(null), + log_name: Joi.string() + .optional() + .allow('') + .allow(null) + .pattern(/^[a-zA-Z0-9_-]+$/) + .max(100) + .messages({ + 'string.pattern.base': '日志名称只能包含字母、数字、下划线和连字符', + 'string.max': '日志名称不能超过100个字符', + }), }; diff --git a/src/locales/en-US.json b/src/locales/en-US.json index 9b7d1ad7..1acaf8ad 100644 --- a/src/locales/en-US.json +++ b/src/locales/en-US.json @@ -524,5 +524,7 @@ "清除成功": "Clean successful", "日志名称": "Log Name", "自定义日志文件夹名称,用于区分不同任务的日志,留空则自动生成": "Custom log folder name to distinguish logs from different tasks. Leave blank to auto-generate", - "请输入自定义日志文件夹名称": "Please enter a custom log folder name" + "请输入自定义日志文件夹名称": "Please enter a custom log folder name", + "日志名称只能包含字母、数字、下划线和连字符": "Log name can only contain letters, numbers, underscores and hyphens", + "日志名称不能超过100个字符": "Log name cannot exceed 100 characters" } diff --git a/src/locales/zh-CN.json b/src/locales/zh-CN.json index 42788752..75d40598 100644 --- a/src/locales/zh-CN.json +++ b/src/locales/zh-CN.json @@ -524,5 +524,7 @@ "清除成功": "清除成功", "日志名称": "日志名称", "自定义日志文件夹名称,用于区分不同任务的日志,留空则自动生成": "自定义日志文件夹名称,用于区分不同任务的日志,留空则自动生成", - "请输入自定义日志文件夹名称": "请输入自定义日志文件夹名称" + "请输入自定义日志文件夹名称": "请输入自定义日志文件夹名称", + "日志名称只能包含字母、数字、下划线和连字符": "日志名称只能包含字母、数字、下划线和连字符", + "日志名称不能超过100个字符": "日志名称不能超过100个字符" } diff --git a/src/pages/crontab/modal.tsx b/src/pages/crontab/modal.tsx index 7f87f427..34c0f9b9 100644 --- a/src/pages/crontab/modal.tsx +++ b/src/pages/crontab/modal.tsx @@ -186,8 +186,21 @@ const CronModal = ({ tooltip={intl.get( '自定义日志文件夹名称,用于区分不同任务的日志,留空则自动生成', )} + rules={[ + { + pattern: /^[a-zA-Z0-9_-]*$/, + message: intl.get('日志名称只能包含字母、数字、下划线和连字符'), + }, + { + max: 100, + message: intl.get('日志名称不能超过100个字符'), + }, + ]} > - + Date: Sat, 8 Nov 2025 17:39:11 +0000 Subject: [PATCH 5/5] Apply prettier formatting to modified files Co-authored-by: whyour <22700758+whyour@users.noreply.github.com> --- back/data/cron.ts | 2 +- back/services/cron.ts | 3 ++- src/pages/crontab/modal.tsx | 1 - 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/back/data/cron.ts b/back/data/cron.ts index c75707a3..422b58e4 100644 --- a/back/data/cron.ts +++ b/back/data/cron.ts @@ -57,7 +57,7 @@ export enum CrontabStatus { 'disabled', } -export interface CronInstance extends Model, Crontab { } +export interface CronInstance extends Model, Crontab {} export const CrontabModel = sequelize.define('Crontab', { name: { unique: 'compositeIndex', diff --git a/back/services/cron.ts b/back/services/cron.ts index 751e49ce..a6ffea23 100644 --- a/back/services/cron.ts +++ b/back/services/cron.ts @@ -481,7 +481,8 @@ export default class CronService { const sanitizedLogName = log_name ? log_name.replace(/[\/\\\.]/g, '_').replace(/^_+|_+$/g, '') : ''; - const uniqPath = sanitizedLogName || (await getUniqPath(command, `${id}`)); + const uniqPath = + sanitizedLogName || (await getUniqPath(command, `${id}`)); const logTime = dayjs().format('YYYY-MM-DD-HH-mm-ss-SSS'); const logDirPath = path.resolve(config.logPath, `${uniqPath}`); if (log_path?.split('/')?.every((x) => x !== uniqPath)) { diff --git a/src/pages/crontab/modal.tsx b/src/pages/crontab/modal.tsx index 34c0f9b9..ae3552e3 100644 --- a/src/pages/crontab/modal.tsx +++ b/src/pages/crontab/modal.tsx @@ -334,4 +334,3 @@ const CronLabelModal = ({ }; export { CronLabelModal, CronModal as default }; -