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();