diff --git a/config.json b/config.json
index c084879..3e89de1 100644
--- a/config.json
+++ b/config.json
@@ -6,7 +6,7 @@
},
"scheduler": {
"enabled": true,
- "cronTime": "0 9 * * *",
+ "cronTime": "40 08 * * *",
"threshold": 100000,
"description": "每天9点采集当日项目",
"timeRange": "thisMonth"
@@ -16,6 +16,6 @@
"smtpPort": 587,
"smtpUser": "1076597680@qq.com",
"smtpPass": "nfrjdiraqddsjeeh",
- "recipients": "1650243281@qq.com"
+ "recipients": "5482498@qq.com"
}
-}
+}
\ No newline at end of file
diff --git a/disable-all-tasks.bat b/disable-all-tasks.bat
new file mode 100644
index 0000000..907d4ad
--- /dev/null
+++ b/disable-all-tasks.bat
@@ -0,0 +1,13 @@
+@echo off
+chcp 65001 >nul
+cd /d "%~dp0"
+
+echo ========================================
+echo 批量禁用所有任务
+echo ========================================
+echo.
+
+node disable-all-tasks.js
+
+echo.
+pause
diff --git a/disable-all-tasks.js b/disable-all-tasks.js
new file mode 100644
index 0000000..b90d481
--- /dev/null
+++ b/disable-all-tasks.js
@@ -0,0 +1,65 @@
+// 批量禁用所有任务脚本
+// 用法: node disable-all-tasks.js
+
+import Database from 'better-sqlite3';
+import { fileURLToPath } from 'url';
+import { dirname, join } from 'path';
+import { existsSync, mkdirSync } from 'fs';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+
+const DEFAULT_DB_DIR = join(__dirname, 'data');
+const DEFAULT_DB_PATH = join(DEFAULT_DB_DIR, 'results.db');
+const LEGACY_DB_PATH = join(DEFAULT_DB_DIR, 'results.sqlite');
+
+// 确定数据库路径
+let DB_PATH = process.env.APP_DB_PATH || process.env.RESULTS_DB_PATH;
+if (!DB_PATH) {
+ if (existsSync(DEFAULT_DB_PATH)) {
+ DB_PATH = DEFAULT_DB_PATH;
+ } else if (existsSync(LEGACY_DB_PATH)) {
+ DB_PATH = LEGACY_DB_PATH;
+ } else {
+ DB_PATH = DEFAULT_DB_PATH;
+ }
+}
+
+if (!existsSync(DB_PATH)) {
+ console.error('数据库文件不存在:', DB_PATH);
+ console.log('请确保项目已初始化并运行过至少一次');
+ process.exit(1);
+}
+
+try {
+ const db = new Database(DB_PATH);
+
+ // 先查询当前启用的任务数量
+ const enabledCount = db.prepare('SELECT COUNT(*) as count FROM tasks WHERE enabled = 1').get();
+ console.log(`当前启用的任务数量: ${enabledCount.count}`);
+
+ // 查询总任务数量
+ const totalCount = db.prepare('SELECT COUNT(*) as count FROM tasks').get();
+ console.log(`总任务数量: ${totalCount.count}`);
+
+ if (enabledCount.count === 0) {
+ console.log('没有需要禁用的任务');
+ db.close();
+ process.exit(0);
+ }
+
+ // 执行批量禁用
+ const result = db.prepare('UPDATE tasks SET enabled = 0').run();
+ console.log(`已禁用 ${result.changes} 个任务`);
+
+ // 验证结果
+ const newEnabledCount = db.prepare('SELECT COUNT(*) as count FROM tasks WHERE enabled = 1').get();
+ console.log(`禁用后启用的任务数量: ${newEnabledCount.count}`);
+
+ db.close();
+ console.log('操作完成');
+
+} catch (error) {
+ console.error('操作失败:', error.message);
+ process.exit(1);
+}
diff --git a/logs/server.pid b/logs/server.pid
index 18bd396..2a68a7d 100644
--- a/logs/server.pid
+++ b/logs/server.pid
@@ -1 +1 @@
-22924
+15556
diff --git a/public/index.html b/public/index.html
index 3321474..630cddf 100644
--- a/public/index.html
+++ b/public/index.html
@@ -688,14 +688,51 @@
-
-
+
+
+
+
@@ -1433,6 +1470,123 @@
.replace(/'/g, ''');
}
+ function padSchedulerNumber(value) {
+ return String(value).padStart(2, '0');
+ }
+
+ function normalizeSchedulerTime(value) {
+ const match = /^(\d{1,2}):(\d{1,2})$/.exec(String(value || '').trim());
+ if (!match) return '09:00';
+
+ const hour = Math.min(23, Math.max(0, parseInt(match[1], 10)));
+ const minute = Math.min(59, Math.max(0, parseInt(match[2], 10)));
+ return `${padSchedulerNumber(hour)}:${padSchedulerNumber(minute)}`;
+ }
+
+ function splitSchedulerTime(value) {
+ const [hour, minute] = normalizeSchedulerTime(value).split(':');
+ return { hour, minute };
+ }
+
+ function describeScheduler(mode, time, weekday, monthDay, cronTime) {
+ const normalizedTime = normalizeSchedulerTime(time);
+ const weekdayMap = {
+ '0': '周日',
+ '1': '周一',
+ '2': '周二',
+ '3': '周三',
+ '4': '周四',
+ '5': '周五',
+ '6': '周六',
+ };
+
+ if (mode === 'daily') return `每天 ${normalizedTime}`;
+ if (mode === 'weekly') return `每周${weekdayMap[String(weekday)] || '周一'} ${normalizedTime}`;
+ if (mode === 'monthly') return `每月 ${monthDay} 号 ${normalizedTime}`;
+ return cronTime || '0 9 * * *';
+ }
+
+ function parseSchedulerCron(cronTime) {
+ const normalizedCron = String(cronTime || '0 9 * * *').trim().replace(/\s+/g, ' ');
+ const parts = normalizedCron.split(' ');
+
+ const fallback = {
+ mode: 'custom',
+ time: '09:00',
+ weekday: '1',
+ monthDay: '1',
+ cronTime: normalizedCron || '0 9 * * *',
+ };
+
+ if (parts.length !== 5) return fallback;
+
+ const [minute, hour, dayOfMonth, month, dayOfWeek] = parts;
+ if (!/^\d+$/.test(minute) || !/^\d+$/.test(hour)) return fallback;
+
+ const time = `${padSchedulerNumber(parseInt(hour, 10))}:${padSchedulerNumber(parseInt(minute, 10))}`;
+
+ if (dayOfMonth === '*' && month === '*' && dayOfWeek === '*') {
+ return { mode: 'daily', time, weekday: '1', monthDay: '1', cronTime: normalizedCron };
+ }
+
+ if (dayOfMonth === '*' && month === '*' && /^(0|1|2|3|4|5|6|7)$/.test(dayOfWeek)) {
+ return {
+ mode: 'weekly',
+ time,
+ weekday: dayOfWeek === '7' ? '0' : dayOfWeek,
+ monthDay: '1',
+ cronTime: normalizedCron,
+ };
+ }
+
+ if (/^\d+$/.test(dayOfMonth) && month === '*' && dayOfWeek === '*') {
+ const normalizedMonthDay = Math.min(31, Math.max(1, parseInt(dayOfMonth, 10)));
+ return {
+ mode: 'monthly',
+ time,
+ weekday: '1',
+ monthDay: String(normalizedMonthDay),
+ cronTime: normalizedCron,
+ };
+ }
+
+ return fallback;
+ }
+
+ function buildSchedulerCronFromInputs() {
+ const mode = document.getElementById('cfgScheduleMode').value;
+ const time = normalizeSchedulerTime(document.getElementById('cfgScheduleTime').value);
+ const { hour, minute } = splitSchedulerTime(time);
+ const weekday = document.getElementById('cfgWeekday').value || '1';
+ const monthDayValue = parseInt(document.getElementById('cfgMonthDay').value, 10);
+ const monthDay = Number.isFinite(monthDayValue) ? Math.min(31, Math.max(1, monthDayValue)) : 1;
+
+ if (mode === 'daily') return `${minute} ${hour} * * *`;
+ if (mode === 'weekly') return `${minute} ${hour} * * ${weekday}`;
+ if (mode === 'monthly') return `${minute} ${hour} ${monthDay} * *`;
+ return document.getElementById('cfgCronTime').value.trim() || '0 9 * * *';
+ }
+
+ function updateSchedulerCronPreview() {
+ const cronTime = buildSchedulerCronFromInputs();
+ document.getElementById('cfgCronTime').value = cronTime;
+ document.getElementById('cfgCronPreview').value = describeScheduler(
+ document.getElementById('cfgScheduleMode').value,
+ document.getElementById('cfgScheduleTime').value,
+ document.getElementById('cfgWeekday').value,
+ document.getElementById('cfgMonthDay').value,
+ cronTime
+ );
+ }
+
+ function handleSchedulerModeChange() {
+ const mode = document.getElementById('cfgScheduleMode').value;
+ document.getElementById('cfgWeekdayGroup').style.display = mode === 'weekly' ? '' : 'none';
+ document.getElementById('cfgMonthDayGroup').style.display = mode === 'monthly' ? '' : 'none';
+ document.getElementById('cfgCustomCronGroup').style.display = mode === 'custom' ? '' : 'none';
+ updateSchedulerCronPreview();
+ }
+
// ===== 设置 =====
async function loadSettings() {
try {
@@ -1448,8 +1602,14 @@
// Scheduler
document.getElementById('cfgSchedulerEnabled').value = String(cfg.scheduler?.enabled ?? false);
- document.getElementById('cfgCronTime').value = cfg.scheduler?.cronTime || '0 9 * * *';
+ const schedulerUi = parseSchedulerCron(cfg.scheduler?.cronTime || '0 9 * * *');
+ document.getElementById('cfgScheduleMode').value = schedulerUi.mode;
+ document.getElementById('cfgScheduleTime').value = schedulerUi.time;
+ document.getElementById('cfgWeekday').value = schedulerUi.weekday;
+ document.getElementById('cfgMonthDay').value = schedulerUi.monthDay;
+ document.getElementById('cfgCronTime').value = schedulerUi.cronTime;
document.getElementById('cfgDescription').value = cfg.scheduler?.description || '';
+ handleSchedulerModeChange();
// Email
document.getElementById('cfgSmtpHost').value = cfg.email?.smtpHost || '';
@@ -1473,6 +1633,14 @@
async function saveSettings() {
try {
+ const cronTime = buildSchedulerCronFromInputs();
+ if (document.getElementById('cfgScheduleMode').value === 'custom') {
+ const cronParts = cronTime.trim().split(/\s+/);
+ if (cronParts.length !== 5) {
+ throw new Error('Cron 表达式格式错误,请填写 5 段');
+ }
+ }
+
const cfg = {
agent: {
baseUrl: document.getElementById('cfgBaseUrl').value.trim(),
@@ -1481,7 +1649,7 @@
},
scheduler: {
enabled: document.getElementById('cfgSchedulerEnabled').value === 'true',
- cronTime: document.getElementById('cfgCronTime').value.trim() || '0 9 * * *',
+ cronTime,
description: document.getElementById('cfgDescription').value.trim(),
},
email: {
@@ -1545,6 +1713,7 @@
// ===== 初始化 =====
initProjectSearchInputs();
loadTasks();
+ handleSchedulerModeChange();
loadSettings();