Skip to content

Commit 06aa073

Browse files
committed
修复日志目录逻辑
1 parent 4cb9f57 commit 06aa073

File tree

7 files changed

+64
-91
lines changed

7 files changed

+64
-91
lines changed

back/data/env.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,5 @@ export const EnvModel = sequelize.define<EnvInstance>('Env', {
4444
position: DataTypes.NUMBER,
4545
name: { type: DataTypes.STRING, unique: 'compositeIndex' },
4646
remarks: DataTypes.STRING,
47-
isPinned: { type: DataTypes.NUMBER, field: 'is_pinned' },
47+
isPinned: DataTypes.NUMBER,
4848
});

back/loaders/db.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export default async () => {
6262
);
6363
} catch (error) {}
6464
try {
65-
await sequelize.query('alter table Envs add column is_pinned NUMBER');
65+
await sequelize.query('alter table Envs add column isPinned NUMBER');
6666
} catch (error) {}
6767

6868
Logger.info('✌️ DB loaded');

back/services/cron.ts

Lines changed: 33 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import { ScheduleType } from '../interface/schedule';
2727

2828
@Service()
2929
export default class CronService {
30-
constructor(@Inject('logger') private logger: winston.Logger) {}
30+
constructor(@Inject('logger') private logger: winston.Logger) { }
3131

3232
private isNodeCron(cron: Crontab) {
3333
const { schedule, extra_schedules } = cron;
@@ -49,9 +49,27 @@ export default class CronService {
4949
return this.isOnceSchedule(schedule) || this.isBootSchedule(schedule);
5050
}
5151

52+
private async getLogName(cron: Crontab) {
53+
const { log_name, command, id } = cron;
54+
if (log_name === '/dev/null') {
55+
return log_name;
56+
}
57+
let uniqPath = await getUniqPath(command, `${id}`);
58+
if (log_name) {
59+
const normalizedLogName = log_name.startsWith('/') ? log_name : path.join(config.logPath, log_name);
60+
if (normalizedLogName.startsWith(config.logPath)) {
61+
uniqPath = log_name;
62+
}
63+
}
64+
const logDirPath = path.resolve(config.logPath, `${uniqPath}`);
65+
await fs.mkdir(logDirPath, { recursive: true });
66+
return uniqPath;
67+
}
68+
5269
public async create(payload: Crontab): Promise<Crontab> {
5370
const tab = new Crontab(payload);
5471
tab.saved = false;
72+
tab.log_name = await this.getLogName(tab);
5573
const doc = await this.insert(tab);
5674

5775
if (isDemoEnv()) {
@@ -82,6 +100,7 @@ export default class CronService {
82100
const doc = await this.getDb({ id: payload.id });
83101
const tab = new Crontab({ ...doc, ...payload });
84102
tab.saved = false;
103+
tab.log_name = await this.getLogName(tab);
85104
const newDoc = await this.updateDb(tab);
86105

87106
if (doc.isDisabled === 1 || isDemoEnv()) {
@@ -142,7 +161,7 @@ export default class CronService {
142161
let cron;
143162
try {
144163
cron = await this.getDb({ id });
145-
} catch (err) {}
164+
} catch (err) { }
146165
if (!cron) {
147166
continue;
148167
}
@@ -476,61 +495,14 @@ export default class CronService {
476495
`[panel][开始执行任务] 参数: ${JSON.stringify(params)}`,
477496
);
478497

479-
let { id, command, log_path, log_name } = cron;
480-
481-
// Check if log_name is an absolute path
482-
const isAbsolutePath = log_name && log_name.startsWith('/');
483-
484-
let uniqPath: string;
485-
let absolutePath: string;
486-
let logPath: string;
487-
488-
if (isAbsolutePath) {
489-
// Special case: /dev/null is allowed as-is to discard logs
490-
if (log_name === '/dev/null') {
491-
uniqPath = log_name;
492-
absolutePath = log_name;
493-
logPath = log_name;
494-
} else {
495-
// For other absolute paths, ensure they are within the safe log directory
496-
const normalizedLogName = path.normalize(log_name!);
497-
const normalizedLogPath = path.normalize(config.logPath);
498-
499-
if (!normalizedLogName.startsWith(normalizedLogPath)) {
500-
this.logger.error(
501-
`[panel][日志路径安全检查失败] 绝对路径必须在日志目录内: ${log_name}`,
502-
);
503-
// Fallback to auto-generated path for security
504-
const fallbackUniqPath = await getUniqPath(command, `${id}`);
505-
const logTime = dayjs().format('YYYY-MM-DD-HH-mm-ss-SSS');
506-
const logDirPath = path.resolve(config.logPath, `${fallbackUniqPath}`);
507-
if (log_path?.split('/')?.every((x) => x !== fallbackUniqPath)) {
508-
await fs.mkdir(logDirPath, { recursive: true });
509-
}
510-
logPath = `${fallbackUniqPath}/${logTime}.log`;
511-
absolutePath = path.resolve(config.logPath, `${logPath}`);
512-
uniqPath = fallbackUniqPath;
513-
} else {
514-
// Absolute path is safe, use it
515-
uniqPath = log_name!;
516-
absolutePath = log_name!;
517-
logPath = log_name!;
518-
}
519-
}
520-
} else {
521-
// Sanitize log_name to prevent path traversal for relative paths
522-
const sanitizedLogName = log_name
523-
? log_name.replace(/[\/\\\.]/g, '_').replace(/^_+|_+$/g, '')
524-
: '';
525-
uniqPath = sanitizedLogName || (await getUniqPath(command, `${id}`));
526-
const logTime = dayjs().format('YYYY-MM-DD-HH-mm-ss-SSS');
527-
const logDirPath = path.resolve(config.logPath, `${uniqPath}`);
528-
if (log_path?.split('/')?.every((x) => x !== uniqPath)) {
529-
await fs.mkdir(logDirPath, { recursive: true });
530-
}
531-
logPath = `${uniqPath}/${logTime}.log`;
532-
absolutePath = path.resolve(config.logPath, `${logPath}`);
533-
}
498+
let { id, command, log_name } = cron;
499+
500+
const uniqPath = log_name === '/dev/null' ? (await getUniqPath(command, `${id}`)) : log_name;
501+
const logTime = dayjs().format('YYYY-MM-DD-HH-mm-ss-SSS');
502+
const logDirPath = path.resolve(config.logPath, `${uniqPath}`);
503+
await fs.mkdir(logDirPath, { recursive: true });
504+
const logPath = `${uniqPath}/${logTime}.log`;
505+
const absolutePath = path.resolve(config.logPath, `${logPath}`);
534506
const cp = spawn(
535507
`real_log_path=${logPath} no_delay=true ${this.makeCommand(
536508
cron,
@@ -610,7 +582,9 @@ export default class CronService {
610582
if (!doc) {
611583
return '';
612584
}
613-
585+
if (doc.log_name === '/dev/null') {
586+
return '日志设置为忽略';
587+
}
614588
const absolutePath = path.resolve(config.logPath, `${doc.log_path}`);
615589
const logFileExist = doc.log_path && (await fileExist(absolutePath));
616590
if (logFileExist) {
@@ -653,9 +627,7 @@ export default class CronService {
653627
if (!command.startsWith(TASK_PREFIX) && !command.startsWith(QL_PREFIX)) {
654628
command = `${TASK_PREFIX}${tab.command}`;
655629
}
656-
let commandVariable = `real_time=${Boolean(realTime)} no_tee=true ID=${
657-
tab.id
658-
} `;
630+
let commandVariable = `real_time=${Boolean(realTime)} log_name=${tab.log_name} no_tee=true ID=${tab.id} `;
659631
if (tab.task_before) {
660632
commandVariable += `task_before='${tab.task_before
661633
.replace(/'/g, "'\\''")

back/validation/schedule.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,27 +45,26 @@ export const commonCronSchema = {
4545
.allow(null)
4646
.custom((value, helpers) => {
4747
if (!value) return value;
48-
48+
4949
// Check if it's an absolute path
5050
if (value.startsWith('/')) {
5151
// Allow /dev/null as special case
5252
if (value === '/dev/null') {
5353
return value;
5454
}
55-
55+
5656
// For other absolute paths, ensure they are within the safe log directory
5757
const normalizedValue = path.normalize(value);
5858
const normalizedLogPath = path.normalize(config.logPath);
59-
59+
6060
if (!normalizedValue.startsWith(normalizedLogPath)) {
6161
return helpers.error('string.unsafePath');
6262
}
63-
63+
6464
return value;
6565
}
66-
67-
// For relative names, enforce strict pattern
68-
if (!/^[a-zA-Z0-9_-]+$/.test(value)) {
66+
67+
if (!/^(?!.*(?:^|\/)\.{1,2}(?:\/|$))(?:\/)?(?:[\w.-]+\/)*[\w.-]+\/?$/.test(value)) {
6968
return helpers.error('string.pattern.base');
7069
}
7170
if (value.length > 100) {

shell/task.sh

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,18 +46,22 @@ handle_log_path() {
4646

4747
time=$(date "+$mtime_format")
4848
log_time=$(format_log_time "$mtime_format" "$time")
49-
log_dir_tmp="${file_param##*/}"
50-
if [[ $file_param =~ "/" ]]; then
51-
if [[ $file_param == /* ]]; then
52-
log_dir_tmp_path="${file_param:1}"
53-
else
54-
log_dir_tmp_path="${file_param}"
49+
if [[ -z $log_name ]]; then
50+
log_dir_tmp="${file_param##*/}"
51+
if [[ $file_param =~ "/" ]]; then
52+
if [[ $file_param == /* ]]; then
53+
log_dir_tmp_path="${file_param:1}"
54+
else
55+
log_dir_tmp_path="${file_param}"
56+
fi
5557
fi
58+
log_dir_tmp_path="${log_dir_tmp_path%/*}"
59+
log_dir_tmp_path="${log_dir_tmp_path##*/}"
60+
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
61+
log_dir="${log_dir_tmp%.*}${suffix}"
62+
else
63+
log_dir="$log_name"
5664
fi
57-
log_dir_tmp_path="${log_dir_tmp_path%/*}"
58-
log_dir_tmp_path="${log_dir_tmp_path##*/}"
59-
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
60-
log_dir="${log_dir_tmp%.*}${suffix}"
6165
log_path="$log_dir/$log_time.log"
6266

6367
if [[ ${real_log_path:=} ]]; then
@@ -73,6 +77,11 @@ handle_log_path() {
7377
if [[ "${real_time:=}" == "true" ]]; then
7478
cmd=""
7579
fi
80+
81+
if [[ "${log_dir:=}" == "/dev/null" ]]; then
82+
cmd=">> /dev/null"
83+
log_path="/dev/null"
84+
fi
7685
}
7786

7887
format_params() {

src/pages/crontab/logModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ const CronLogModal = ({
5555
const log = data as string;
5656
setValue(log || intl.get("暂无日志"));
5757
const hasNext = Boolean(
58-
log && !logEnded(log) && !log.includes("日志不存在"),
58+
log && !logEnded(log) && !log.includes("日志不存在") && !log.includes("日志设置为忽略"),
5959
);
6060
if (!hasNext && !logEnded(value) && value !== intl.get("启动中...")) {
6161
setTimeout(() => {

src/pages/crontab/modal.tsx

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -190,22 +190,15 @@ const CronModal = ({
190190
{
191191
validator: (_, value) => {
192192
if (!value) return Promise.resolve();
193-
// Allow /dev/null specifically
194193
if (value === '/dev/null') return Promise.resolve();
195-
// Warn about other absolute paths (server will validate)
196-
if (value.startsWith('/')) {
197-
// We can't validate the exact path on frontend, but inform user
198-
return Promise.resolve();
194+
if (value.length > 100) {
195+
return Promise.reject(intl.get('日志名称不能超过100个字符'));
199196
}
200-
// For relative names, enforce strict pattern
201-
if (!/^[a-zA-Z0-9_-]+$/.test(value)) {
197+
if (!/^(?!.*(?:^|\/)\.{1,2}(?:\/|$))(?:\/)?(?:[\w.-]+\/)*[\w.-]+\/?$/.test(value)) {
202198
return Promise.reject(
203199
intl.get('日志名称只能包含字母、数字、下划线和连字符'),
204200
);
205201
}
206-
if (value.length > 100) {
207-
return Promise.reject(intl.get('日志名称不能超过100个字符'));
208-
}
209202
return Promise.resolve();
210203
},
211204
},

0 commit comments

Comments
 (0)