From dda4edb95277ec085f651e33d0365a8fa7e727c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AC=E6=98=8E?= <83812544+Ed1s0nZ@users.noreply.github.com> Date: Wed, 15 Apr 2026 00:08:35 +0800 Subject: [PATCH] Add files via upload --- web/static/css/style.css | 23 +++++++++++++++++----- web/static/i18n/en-US.json | 4 ++++ web/static/i18n/zh-CN.json | 4 ++++ web/static/js/tasks.js | 40 ++++++++++++++++++++++++++++++++++---- web/templates/index.html | 7 +++++++ 5 files changed, 69 insertions(+), 9 deletions(-) diff --git a/web/static/css/style.css b/web/static/css/style.css index e49e162b..32526443 100644 --- a/web/static/css/style.css +++ b/web/static/css/style.css @@ -3621,7 +3621,7 @@ header { color: var(--text-primary); } -.form-group input, +.form-group input:not([type="checkbox"]):not([type="radio"]), .form-group select { padding: 10px 12px; border: 1px solid var(--border-color); @@ -3644,30 +3644,43 @@ header { padding-right: 36px; } -.form-group input:focus, +.form-group input:not([type="checkbox"]):not([type="radio"]):focus, .form-group select:focus { outline: none; border-color: var(--accent-color); box-shadow: 0 0 0 3px rgba(0, 102, 255, 0.1); } -.form-group input:hover, +.form-group input:not([type="checkbox"]):not([type="radio"]):hover, .form-group select:hover { border-color: var(--accent-color); } -.form-group input.error, +.form-group input:not([type="checkbox"]):not([type="radio"]).error, .form-group select.error { border-color: var(--error-color); box-shadow: 0 0 0 3px rgba(220, 53, 69, 0.1); } -.form-group input.error:focus, +.form-group input:not([type="checkbox"]):not([type="radio"]).error:focus, .form-group select.error:focus { border-color: var(--error-color); box-shadow: 0 0 0 3px rgba(220, 53, 69, 0.2); } +.batch-execute-now-label { + display: inline-flex; + align-items: center; + gap: 8px; + width: fit-content; + cursor: pointer; +} + +.batch-execute-now-label input[type="checkbox"] { + width: auto; + margin: 0; +} + /* 现代化复选框样式 */ .checkbox-label { display: flex !important; diff --git a/web/static/i18n/en-US.json b/web/static/i18n/en-US.json index 4d8a9609..419d233a 100644 --- a/web/static/i18n/en-US.json +++ b/web/static/i18n/en-US.json @@ -1517,6 +1517,8 @@ "cronExprPlaceholder": "e.g. 0 */2 * * * (run every 2 hours)", "cronExprHint": "Use standard 5-field Cron: minute hour day month weekday. Example: `0 2 * * *` runs at 02:00 daily.", "cronExprRequired": "Please fill in a Cron expression when Cron schedule is selected", + "executeNow": "Run immediately after creation", + "executeNowHint": "Default is off. When disabled, the queue stays pending and can be started manually later.", "tasksList": "Task list (one task per line)", "tasksListPlaceholder": "Enter task list, one per line", "tasksListPlaceholderExample": "Enter task list, one per line, for example:\nScan open ports of 192.168.1.1\nCheck if https://example.com has SQL injection\nEnumerate subdomains of example.com", @@ -1528,6 +1530,8 @@ "title": "Batch queue details", "addTask": "Add task", "startExecute": "Start", + "startExecuteNow": "Run now (one round)", + "startExecuteNowConfirm": "This is a Cron queue. Clicking Start will run the current round immediately instead of waiting for the next Cron time. Continue?", "pauseQueue": "Pause queue", "deleteQueue": "Delete queue", "queueTitle": "Task title", diff --git a/web/static/i18n/zh-CN.json b/web/static/i18n/zh-CN.json index bf99d795..f0a81576 100644 --- a/web/static/i18n/zh-CN.json +++ b/web/static/i18n/zh-CN.json @@ -1517,6 +1517,8 @@ "cronExprPlaceholder": "例如:0 */2 * * *(每2小时执行一次)", "cronExprHint": "采用标准 5 段 Cron:分 时 日 月 周,例如 `0 2 * * *` 表示每天 02:00 执行。", "cronExprRequired": "请选择 Cron 调度后填写 Cron 表达式", + "executeNow": "创建后立即执行", + "executeNowHint": "默认不立即执行;关闭后队列保持待执行,可在需要时手动开始。", "tasksList": "任务列表(每行一个任务)", "tasksListPlaceholder": "请输入任务列表,每行一个任务", "tasksListPlaceholderExample": "请输入任务列表,每行一个任务,例如:\n扫描 192.168.1.1 的开放端口\n检查 https://example.com 是否存在SQL注入\n枚举 example.com 的子域名", @@ -1528,6 +1530,8 @@ "title": "批量任务队列详情", "addTask": "添加任务", "startExecute": "开始执行", + "startExecuteNow": "立即执行一轮", + "startExecuteNowConfirm": "这是 Cron 队列,点击后会立即执行当前这一轮,不会等待下次 Cron 时间。确定立即执行吗?", "pauseQueue": "暂停队列", "deleteQueue": "删除队列", "queueTitle": "任务标题", diff --git a/web/static/js/tasks.js b/web/static/js/tasks.js index 52964a9d..bd644b4e 100644 --- a/web/static/js/tasks.js +++ b/web/static/js/tasks.js @@ -57,6 +57,15 @@ function getBatchQueueStatusPresentation(queue) { return { ...base, ...empty }; } +/** 队列是否处于「可改子任务列表/文案」的空闲态(与后端 batch_task_manager.queueAllowsTaskListMutationLocked 对齐) */ +function batchQueueAllowsSubtaskMutation(queue) { + if (!queue) return false; + if (queue.status === 'running') return false; + const hasRunningSubtask = Array.isArray(queue.tasks) && queue.tasks.some(t => t && t.status === 'running'); + if (hasRunningSubtask) return false; + return queue.status === 'pending' || queue.status === 'paused' || queue.status === 'completed' || queue.status === 'cancelled'; +} + // HTML转义函数(如果未定义) if (typeof escapeHtml === 'undefined') { function escapeHtml(text) { @@ -782,6 +791,7 @@ async function showBatchImportModal() { const agentModeSelect = document.getElementById('batch-queue-agent-mode'); const scheduleModeSelect = document.getElementById('batch-queue-schedule-mode'); const cronExprInput = document.getElementById('batch-queue-cron-expr'); + const executeNowCheckbox = document.getElementById('batch-queue-execute-now'); if (modal && input) { input.value = ''; if (titleInput) { @@ -800,6 +810,9 @@ async function showBatchImportModal() { if (cronExprInput) { cronExprInput.value = ''; } + if (executeNowCheckbox) { + executeNowCheckbox.checked = false; + } handleBatchScheduleModeChange(); updateBatchImportStats(''); @@ -895,6 +908,7 @@ async function createBatchQueue() { const agentModeSelect = document.getElementById('batch-queue-agent-mode'); const scheduleModeSelect = document.getElementById('batch-queue-schedule-mode'); const cronExprInput = document.getElementById('batch-queue-cron-expr'); + const executeNowCheckbox = document.getElementById('batch-queue-execute-now'); if (!input) return; const text = input.value.trim(); @@ -918,6 +932,7 @@ async function createBatchQueue() { const agentMode = agentModeSelect ? (agentModeSelect.value === 'multi' ? 'multi' : 'single') : 'single'; const scheduleMode = scheduleModeSelect ? (scheduleModeSelect.value === 'cron' ? 'cron' : 'manual') : 'manual'; const cronExpr = cronExprInput ? cronExprInput.value.trim() : ''; + const executeNow = executeNowCheckbox ? !!executeNowCheckbox.checked : false; if (scheduleMode === 'cron' && !cronExpr) { alert(_t('batchImportModal.cronExprRequired')); return; @@ -929,7 +944,7 @@ async function createBatchQueue() { headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify({ title, tasks, role, agentMode, scheduleMode, cronExpr }), + body: JSON.stringify({ title, tasks, role, agentMode, scheduleMode, cronExpr, executeNow }), }); if (!response.ok) { @@ -1266,6 +1281,7 @@ async function showBatchQueueDetail(queueId) { const queue = result.queue; batchQueuesState.currentQueueId = queueId; const pres = getBatchQueueStatusPresentation(queue); + const allowSubtaskMutation = batchQueueAllowsSubtaskMutation(queue); if (title) { // textContent 本身会做转义;这里不要再 escapeHtml,否则会把 && 显示成 &...(看起来像“变形/乱码”) @@ -1275,7 +1291,7 @@ async function showBatchQueueDetail(queueId) { // 更新按钮显示 const pauseBtn = document.getElementById('batch-queue-pause-btn'); if (addTaskBtn) { - addTaskBtn.style.display = queue.status === 'pending' ? 'inline-block' : 'none'; + addTaskBtn.style.display = allowSubtaskMutation ? 'inline-block' : 'none'; } if (startBtn) { // pending状态显示"开始执行",paused状态显示"继续执行" @@ -1283,7 +1299,10 @@ async function showBatchQueueDetail(queueId) { if (startBtn && queue.status === 'paused') { startBtn.textContent = _t('tasks.resumeExecute'); } else if (startBtn && queue.status === 'pending') { - startBtn.textContent = _t('batchQueueDetailModal.startExecute'); + const isCronPending = queue.scheduleMode === 'cron' && queue.scheduleEnabled !== false; + startBtn.textContent = isCronPending + ? _t('batchQueueDetailModal.startExecuteNow') + : _t('batchQueueDetailModal.startExecute'); } } if (pauseBtn) { @@ -1374,7 +1393,7 @@ async function showBatchQueueDetail(queueId) {