From 6bd558cbd4d176b60d32113db4b917c3c54c2829 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, 7 Jan 2026 19:38:36 +0800
Subject: [PATCH] Add files via upload
---
internal/database/batch_task.go | 31 +++++++++++++-------------
internal/database/database.go | 31 ++++++++++++++++++++++++++
internal/handler/agent.go | 3 ++-
internal/handler/batch_task_manager.go | 17 ++++++++++----
web/static/css/style.css | 1 -
web/static/js/tasks.js | 19 ++++++++++++++--
web/templates/index.html | 12 ++++++----
7 files changed, 87 insertions(+), 27 deletions(-)
diff --git a/internal/database/batch_task.go b/internal/database/batch_task.go
index 1847c73d..22a79eef 100644
--- a/internal/database/batch_task.go
+++ b/internal/database/batch_task.go
@@ -11,6 +11,7 @@ import (
// BatchTaskQueueRow 批量任务队列数据库行
type BatchTaskQueueRow struct {
ID string
+ Title sql.NullString
Status string
CreatedAt time.Time
StartedAt sql.NullTime
@@ -32,7 +33,7 @@ type BatchTaskRow struct {
}
// CreateBatchQueue 创建批量任务队列
-func (db *DB) CreateBatchQueue(queueID string, tasks []map[string]interface{}) error {
+func (db *DB) CreateBatchQueue(queueID string, title string, tasks []map[string]interface{}) error {
tx, err := db.Begin()
if err != nil {
return fmt.Errorf("开始事务失败: %w", err)
@@ -41,8 +42,8 @@ func (db *DB) CreateBatchQueue(queueID string, tasks []map[string]interface{}) e
now := time.Now()
_, err = tx.Exec(
- "INSERT INTO batch_task_queues (id, status, created_at, current_index) VALUES (?, ?, ?, ?)",
- queueID, "pending", now, 0,
+ "INSERT INTO batch_task_queues (id, title, status, created_at, current_index) VALUES (?, ?, ?, ?, ?)",
+ queueID, title, "pending", now, 0,
)
if err != nil {
return fmt.Errorf("创建批量任务队列失败: %w", err)
@@ -76,9 +77,9 @@ func (db *DB) GetBatchQueue(queueID string) (*BatchTaskQueueRow, error) {
var row BatchTaskQueueRow
var createdAt string
err := db.QueryRow(
- "SELECT id, status, created_at, started_at, completed_at, current_index FROM batch_task_queues WHERE id = ?",
+ "SELECT id, title, status, created_at, started_at, completed_at, current_index FROM batch_task_queues WHERE id = ?",
queueID,
- ).Scan(&row.ID, &row.Status, &createdAt, &row.StartedAt, &row.CompletedAt, &row.CurrentIndex)
+ ).Scan(&row.ID, &row.Title, &row.Status, &createdAt, &row.StartedAt, &row.CompletedAt, &row.CurrentIndex)
if err == sql.ErrNoRows {
return nil, nil
}
@@ -102,7 +103,7 @@ func (db *DB) GetBatchQueue(queueID string) (*BatchTaskQueueRow, error) {
// GetAllBatchQueues 获取所有批量任务队列
func (db *DB) GetAllBatchQueues() ([]*BatchTaskQueueRow, error) {
rows, err := db.Query(
- "SELECT id, status, created_at, started_at, completed_at, current_index FROM batch_task_queues ORDER BY created_at DESC",
+ "SELECT id, title, status, created_at, started_at, completed_at, current_index FROM batch_task_queues ORDER BY created_at DESC",
)
if err != nil {
return nil, fmt.Errorf("查询批量任务队列列表失败: %w", err)
@@ -113,7 +114,7 @@ func (db *DB) GetAllBatchQueues() ([]*BatchTaskQueueRow, error) {
for rows.Next() {
var row BatchTaskQueueRow
var createdAt string
- if err := rows.Scan(&row.ID, &row.Status, &createdAt, &row.StartedAt, &row.CompletedAt, &row.CurrentIndex); err != nil {
+ if err := rows.Scan(&row.ID, &row.Title, &row.Status, &createdAt, &row.StartedAt, &row.CompletedAt, &row.CurrentIndex); err != nil {
return nil, fmt.Errorf("扫描批量任务队列失败: %w", err)
}
parsedTime, parseErr := time.Parse("2006-01-02 15:04:05", createdAt)
@@ -133,7 +134,7 @@ func (db *DB) GetAllBatchQueues() ([]*BatchTaskQueueRow, error) {
// ListBatchQueues 列出批量任务队列(支持筛选和分页)
func (db *DB) ListBatchQueues(limit, offset int, status, keyword string) ([]*BatchTaskQueueRow, error) {
- query := "SELECT id, status, created_at, started_at, completed_at, current_index FROM batch_task_queues WHERE 1=1"
+ query := "SELECT id, title, status, created_at, started_at, completed_at, current_index FROM batch_task_queues WHERE 1=1"
args := []interface{}{}
// 状态筛选
@@ -142,10 +143,10 @@ func (db *DB) ListBatchQueues(limit, offset int, status, keyword string) ([]*Bat
args = append(args, status)
}
- // 关键字搜索(搜索队列ID)
+ // 关键字搜索(搜索队列ID和标题)
if keyword != "" {
- query += " AND id LIKE ?"
- args = append(args, "%"+keyword+"%")
+ query += " AND (id LIKE ? OR title LIKE ?)"
+ args = append(args, "%"+keyword+"%", "%"+keyword+"%")
}
query += " ORDER BY created_at DESC LIMIT ? OFFSET ?"
@@ -161,7 +162,7 @@ func (db *DB) ListBatchQueues(limit, offset int, status, keyword string) ([]*Bat
for rows.Next() {
var row BatchTaskQueueRow
var createdAt string
- if err := rows.Scan(&row.ID, &row.Status, &createdAt, &row.StartedAt, &row.CompletedAt, &row.CurrentIndex); err != nil {
+ if err := rows.Scan(&row.ID, &row.Title, &row.Status, &createdAt, &row.StartedAt, &row.CompletedAt, &row.CurrentIndex); err != nil {
return nil, fmt.Errorf("扫描批量任务队列失败: %w", err)
}
parsedTime, parseErr := time.Parse("2006-01-02 15:04:05", createdAt)
@@ -190,10 +191,10 @@ func (db *DB) CountBatchQueues(status, keyword string) (int, error) {
args = append(args, status)
}
- // 关键字搜索
+ // 关键字搜索(搜索队列ID和标题)
if keyword != "" {
- query += " AND id LIKE ?"
- args = append(args, "%"+keyword+"%")
+ query += " AND (id LIKE ? OR title LIKE ?)"
+ args = append(args, "%"+keyword+"%", "%"+keyword+"%")
}
var count int
diff --git a/internal/database/database.go b/internal/database/database.go
index 4b962e3b..5b3cd137 100644
--- a/internal/database/database.go
+++ b/internal/database/database.go
@@ -193,6 +193,7 @@ func (db *DB) initTables() error {
createBatchTaskQueuesTable := `
CREATE TABLE IF NOT EXISTS batch_task_queues (
id TEXT PRIMARY KEY,
+ title TEXT,
status TEXT NOT NULL,
created_at DATETIME NOT NULL,
started_at DATETIME,
@@ -240,6 +241,7 @@ func (db *DB) initTables() error {
CREATE INDEX IF NOT EXISTS idx_vulnerabilities_created_at ON vulnerabilities(created_at);
CREATE INDEX IF NOT EXISTS idx_batch_tasks_queue_id ON batch_tasks(queue_id);
CREATE INDEX IF NOT EXISTS idx_batch_task_queues_created_at ON batch_task_queues(created_at);
+ CREATE INDEX IF NOT EXISTS idx_batch_task_queues_title ON batch_task_queues(title);
`
if _, err := db.Exec(createConversationsTable); err != nil {
@@ -310,6 +312,11 @@ func (db *DB) initTables() error {
// 不返回错误,允许继续运行
}
+ if err := db.migrateBatchTaskQueuesTable(); err != nil {
+ db.logger.Warn("迁移batch_task_queues表失败", zap.Error(err))
+ // 不返回错误,允许继续运行
+ }
+
if _, err := db.Exec(createIndexes); err != nil {
return fmt.Errorf("创建索引失败: %w", err)
}
@@ -426,6 +433,30 @@ func (db *DB) migrateConversationGroupMappingsTable() error {
return nil
}
+// migrateBatchTaskQueuesTable 迁移batch_task_queues表,添加title字段
+func (db *DB) migrateBatchTaskQueuesTable() error {
+ // 检查title字段是否存在
+ var count int
+ err := db.QueryRow("SELECT COUNT(*) FROM pragma_table_info('batch_task_queues') WHERE name='title'").Scan(&count)
+ if err != nil {
+ // 如果查询失败,尝试添加字段
+ if _, addErr := db.Exec("ALTER TABLE batch_task_queues ADD COLUMN title TEXT"); addErr != nil {
+ // 如果字段已存在,忽略错误
+ errMsg := strings.ToLower(addErr.Error())
+ if !strings.Contains(errMsg, "duplicate column") && !strings.Contains(errMsg, "already exists") {
+ db.logger.Warn("添加title字段失败", zap.Error(addErr))
+ }
+ }
+ } else if count == 0 {
+ // 字段不存在,添加它
+ if _, err := db.Exec("ALTER TABLE batch_task_queues ADD COLUMN title TEXT"); err != nil {
+ db.logger.Warn("添加title字段失败", zap.Error(err))
+ }
+ }
+
+ return nil
+}
+
// NewKnowledgeDB 创建知识库数据库连接(只包含知识库相关的表)
func NewKnowledgeDB(dbPath string, logger *zap.Logger) (*DB, error) {
sqlDB, err := sql.Open("sqlite3", dbPath+"?_journal_mode=WAL&_foreign_keys=1")
diff --git a/internal/handler/agent.go b/internal/handler/agent.go
index 738d4618..015cc5ab 100644
--- a/internal/handler/agent.go
+++ b/internal/handler/agent.go
@@ -759,6 +759,7 @@ func (h *AgentHandler) ListCompletedTasks(c *gin.Context) {
// BatchTaskRequest 批量任务请求
type BatchTaskRequest struct {
+ Title string `json:"title"` // 任务标题(可选)
Tasks []string `json:"tasks" binding:"required"` // 任务列表,每行一个任务
}
@@ -788,7 +789,7 @@ func (h *AgentHandler) CreateBatchQueue(c *gin.Context) {
return
}
- queue := h.batchTaskManager.CreateBatchQueue(validTasks)
+ queue := h.batchTaskManager.CreateBatchQueue(req.Title, validTasks)
c.JSON(http.StatusOK, gin.H{
"queueId": queue.ID,
"queue": queue,
diff --git a/internal/handler/batch_task_manager.go b/internal/handler/batch_task_manager.go
index 0b5d8bbd..24ada0c6 100644
--- a/internal/handler/batch_task_manager.go
+++ b/internal/handler/batch_task_manager.go
@@ -28,6 +28,7 @@ type BatchTask struct {
// BatchTaskQueue 批量任务队列
type BatchTaskQueue struct {
ID string `json:"id"`
+ Title string `json:"title,omitempty"`
Tasks []*BatchTask `json:"tasks"`
Status string `json:"status"` // pending, running, paused, completed, cancelled
CreatedAt time.Time `json:"createdAt"`
@@ -61,13 +62,14 @@ func (m *BatchTaskManager) SetDB(db *database.DB) {
}
// CreateBatchQueue 创建批量任务队列
-func (m *BatchTaskManager) CreateBatchQueue(tasks []string) *BatchTaskQueue {
+func (m *BatchTaskManager) CreateBatchQueue(title string, tasks []string) *BatchTaskQueue {
m.mu.Lock()
defer m.mu.Unlock()
queueID := time.Now().Format("20060102150405") + "-" + generateShortID()
queue := &BatchTaskQueue{
ID: queueID,
+ Title: title,
Tasks: make([]*BatchTask, 0, len(tasks)),
Status: "pending",
CreatedAt: time.Now(),
@@ -96,7 +98,7 @@ func (m *BatchTaskManager) CreateBatchQueue(tasks []string) *BatchTaskQueue {
// 保存到数据库
if m.db != nil {
- if err := m.db.CreateBatchQueue(queueID, dbTasks); err != nil {
+ if err := m.db.CreateBatchQueue(queueID, title, dbTasks); err != nil {
// 如果数据库保存失败,记录错误但继续(使用内存缓存)
// 这里可以添加日志记录
}
@@ -153,6 +155,9 @@ func (m *BatchTaskManager) loadQueueFromDB(queueID string) *BatchTaskQueue {
Tasks: make([]*BatchTask, 0, len(taskRows)),
}
+ if queueRow.Title.Valid {
+ queue.Title = queueRow.Title.String
+ }
if queueRow.StartedAt.Valid {
queue.StartedAt = &queueRow.StartedAt.Time
}
@@ -271,11 +276,12 @@ func (m *BatchTaskManager) ListQueues(limit, offset int, status, keyword string)
if status != "" && status != "all" && queue.Status != status {
continue
}
- // 关键字搜索
+ // 关键字搜索(搜索队列ID和标题)
if keyword != "" {
keywordLower := strings.ToLower(keyword)
queueIDLower := strings.ToLower(queue.ID)
- if !strings.Contains(queueIDLower, keywordLower) {
+ queueTitleLower := strings.ToLower(queue.Title)
+ if !strings.Contains(queueIDLower, keywordLower) && !strings.Contains(queueTitleLower, keywordLower) {
// 也可以搜索创建时间
createdAtStr := queue.CreatedAt.Format("2006-01-02 15:04:05")
if !strings.Contains(createdAtStr, keyword) {
@@ -342,6 +348,9 @@ func (m *BatchTaskManager) LoadFromDB() error {
Tasks: make([]*BatchTask, 0, len(taskRows)),
}
+ if queueRow.Title.Valid {
+ queue.Title = queueRow.Title.String
+ }
if queueRow.StartedAt.Valid {
queue.StartedAt = &queueRow.StartedAt.Time
}
diff --git a/web/static/css/style.css b/web/static/css/style.css
index 7adb409e..5ef913c2 100644
--- a/web/static/css/style.css
+++ b/web/static/css/style.css
@@ -6615,7 +6615,6 @@ header {
align-items: center;
margin-bottom: 16px;
padding-bottom: 12px;
- border-bottom: 1px solid var(--border-color);
}
.batch-queues-header h3 {
diff --git a/web/static/js/tasks.js b/web/static/js/tasks.js
index e12ae874..cd01fe74 100644
--- a/web/static/js/tasks.js
+++ b/web/static/js/tasks.js
@@ -720,8 +720,12 @@ const batchQueuesState = {
function showBatchImportModal() {
const modal = document.getElementById('batch-import-modal');
const input = document.getElementById('batch-tasks-input');
+ const titleInput = document.getElementById('batch-queue-title');
if (modal && input) {
input.value = '';
+ if (titleInput) {
+ titleInput.value = '';
+ }
updateBatchImportStats('');
modal.style.display = 'block';
input.focus();
@@ -765,6 +769,7 @@ document.addEventListener('DOMContentLoaded', function() {
// 创建批量任务队列
async function createBatchQueue() {
const input = document.getElementById('batch-tasks-input');
+ const titleInput = document.getElementById('batch-queue-title');
if (!input) return;
const text = input.value.trim();
@@ -780,13 +785,16 @@ async function createBatchQueue() {
return;
}
+ // 获取标题(可选)
+ const title = titleInput ? titleInput.value.trim() : '';
+
try {
const response = await apiFetch('/api/batch-tasks', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
- body: JSON.stringify({ tasks }),
+ body: JSON.stringify({ title, tasks }),
});
if (!response.ok) {
@@ -918,10 +926,13 @@ function renderBatchQueues() {
// 允许删除待执行、已完成或已取消状态的队列
const canDelete = queue.status === 'pending' || queue.status === 'completed' || queue.status === 'cancelled';
+ const titleDisplay = queue.title ? `${escapeHtml(queue.title)}` : '';
+
return `