mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-04-21 10:16:32 +02:00
Add files via upload
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+17
-2
@@ -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 ? `<span class="batch-queue-title" style="font-weight: 600; color: var(--text-primary); margin-right: 8px;">${escapeHtml(queue.title)}</span>` : '';
|
||||
|
||||
return `
|
||||
<div class="batch-queue-item" data-queue-id="${queue.id}" onclick="showBatchQueueDetail('${queue.id}')">
|
||||
<div class="batch-queue-header">
|
||||
<div class="batch-queue-info" style="flex: 1;">
|
||||
${titleDisplay}
|
||||
<span class="batch-queue-status ${status.class}">${status.text}</span>
|
||||
<span class="batch-queue-id">队列ID: ${escapeHtml(queue.id)}</span>
|
||||
<span class="batch-queue-time">创建时间: ${new Date(queue.createdAt).toLocaleString('zh-CN')}</span>
|
||||
@@ -1100,7 +1111,7 @@ async function showBatchQueueDetail(queueId) {
|
||||
batchQueuesState.currentQueueId = queueId;
|
||||
|
||||
if (title) {
|
||||
title.textContent = '批量任务队列';
|
||||
title.textContent = queue.title ? `批量任务队列 - ${escapeHtml(queue.title)}` : '批量任务队列';
|
||||
}
|
||||
|
||||
// 更新按钮显示
|
||||
@@ -1146,6 +1157,10 @@ async function showBatchQueueDetail(queueId) {
|
||||
|
||||
content.innerHTML = `
|
||||
<div class="batch-queue-detail-info">
|
||||
${queue.title ? `<div class="detail-item">
|
||||
<span class="detail-label">任务标题</span>
|
||||
<span class="detail-value">${escapeHtml(queue.title)}</span>
|
||||
</div>` : ''}
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">队列ID</span>
|
||||
<span class="detail-value"><code>${escapeHtml(queue.id)}</code></span>
|
||||
|
||||
@@ -568,9 +568,6 @@
|
||||
<div class="page-content">
|
||||
<!-- 批量任务队列列表 -->
|
||||
<div class="batch-queues-section" id="batch-queues-section" style="display: none;">
|
||||
<div class="batch-queues-header">
|
||||
<h3>批量任务队列</h3>
|
||||
</div>
|
||||
<!-- 筛选控件 -->
|
||||
<div class="batch-queues-filters tasks-filters">
|
||||
<label>
|
||||
@@ -585,7 +582,7 @@
|
||||
</select>
|
||||
</label>
|
||||
<label style="flex: 1; max-width: 300px;">
|
||||
<span>搜索队列ID或创建时间</span>
|
||||
<span>搜索队列ID、标题或创建时间</span>
|
||||
<input type="text" id="batch-queues-search" placeholder="输入关键字搜索..."
|
||||
oninput="filterBatchQueues()">
|
||||
</label>
|
||||
@@ -1160,6 +1157,13 @@
|
||||
<span class="modal-close" onclick="closeBatchImportModal()">×</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="batch-queue-title">任务标题</label>
|
||||
<input type="text" id="batch-queue-title" placeholder="请输入任务标题(可选,用于标识和筛选)" />
|
||||
<div class="form-hint" style="margin-top: 4px;">
|
||||
为批量任务队列设置一个标题,方便后续查找和管理。
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="batch-tasks-input">任务列表(每行一个任务)<span style="color: red;">*</span></label>
|
||||
<textarea id="batch-tasks-input" rows="15" placeholder="请输入任务列表,每行一个任务,例如: 扫描 192.168.1.1 的开放端口 检查 https://example.com 是否存在SQL注入 枚举 example.com 的子域名" style="font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-size: 0.875rem; line-height: 1.5;"></textarea>
|
||||
|
||||
Reference in New Issue
Block a user