From 3e0867d459dbed371a46565e31e05f0dddd43d7e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=85=AC=E6=98=8E?=
<83812544+Ed1s0nZ@users.noreply.github.com>
Date: Sat, 27 Dec 2025 03:08:01 +0800
Subject: [PATCH] Add files via upload
---
internal/database/vulnerability.go | 31 +++
internal/handler/vulnerability.go | 58 +++++-
web/static/css/style.css | 45 ++++-
web/static/js/vulnerability.js | 310 ++++++++++++++++++++++++++++-
web/templates/index.html | 3 +
5 files changed, 437 insertions(+), 10 deletions(-)
diff --git a/internal/database/vulnerability.go b/internal/database/vulnerability.go
index 1423759b..c4ec69b2 100644
--- a/internal/database/vulnerability.go
+++ b/internal/database/vulnerability.go
@@ -145,6 +145,37 @@ func (db *DB) ListVulnerabilities(limit, offset int, id, conversationID, severit
return vulnerabilities, nil
}
+// CountVulnerabilities 统计漏洞总数(支持筛选条件)
+func (db *DB) CountVulnerabilities(id, conversationID, severity, status string) (int, error) {
+ query := "SELECT COUNT(*) FROM vulnerabilities WHERE 1=1"
+ args := []interface{}{}
+
+ if id != "" {
+ query += " AND id = ?"
+ args = append(args, id)
+ }
+ if conversationID != "" {
+ query += " AND conversation_id = ?"
+ args = append(args, conversationID)
+ }
+ if severity != "" {
+ query += " AND severity = ?"
+ args = append(args, severity)
+ }
+ if status != "" {
+ query += " AND status = ?"
+ args = append(args, status)
+ }
+
+ var count int
+ err := db.QueryRow(query, args...).Scan(&count)
+ if err != nil {
+ return 0, fmt.Errorf("统计漏洞总数失败: %w", err)
+ }
+
+ return count, nil
+}
+
// UpdateVulnerability 更新漏洞
func (db *DB) UpdateVulnerability(id string, vuln *Vulnerability) error {
vuln.UpdatedAt = time.Now()
diff --git a/internal/handler/vulnerability.go b/internal/handler/vulnerability.go
index 3bd42af2..9975efa7 100644
--- a/internal/handler/vulnerability.go
+++ b/internal/handler/vulnerability.go
@@ -82,10 +82,20 @@ func (h *VulnerabilityHandler) GetVulnerability(c *gin.Context) {
c.JSON(http.StatusOK, vuln)
}
+// ListVulnerabilitiesResponse 漏洞列表响应
+type ListVulnerabilitiesResponse struct {
+ Vulnerabilities []*database.Vulnerability `json:"vulnerabilities"`
+ Total int `json:"total"`
+ Page int `json:"page"`
+ PageSize int `json:"page_size"`
+ TotalPages int `json:"total_pages"`
+}
+
// ListVulnerabilities 列出漏洞
func (h *VulnerabilityHandler) ListVulnerabilities(c *gin.Context) {
- limitStr := c.DefaultQuery("limit", "50")
+ limitStr := c.DefaultQuery("limit", "20")
offsetStr := c.DefaultQuery("offset", "0")
+ pageStr := c.Query("page")
id := c.Query("id")
conversationID := c.Query("conversation_id")
severity := c.Query("severity")
@@ -93,11 +103,32 @@ func (h *VulnerabilityHandler) ListVulnerabilities(c *gin.Context) {
limit, _ := strconv.Atoi(limitStr)
offset, _ := strconv.Atoi(offsetStr)
+ page := 1
- if limit <= 0 || limit > 100 {
- limit = 50
+ // 如果提供了page参数,优先使用page计算offset
+ if pageStr != "" {
+ if p, err := strconv.Atoi(pageStr); err == nil && p > 0 {
+ page = p
+ offset = (page - 1) * limit
+ }
}
+ if limit <= 0 || limit > 100 {
+ limit = 20
+ }
+ if offset < 0 {
+ offset = 0
+ }
+
+ // 获取总数
+ total, err := h.db.CountVulnerabilities(id, conversationID, severity, status)
+ if err != nil {
+ h.logger.Error("获取漏洞总数失败", zap.Error(err))
+ // 继续执行,使用0作为总数
+ total = 0
+ }
+
+ // 获取漏洞列表
vulnerabilities, err := h.db.ListVulnerabilities(limit, offset, id, conversationID, severity, status)
if err != nil {
h.logger.Error("获取漏洞列表失败", zap.Error(err))
@@ -105,7 +136,26 @@ func (h *VulnerabilityHandler) ListVulnerabilities(c *gin.Context) {
return
}
- c.JSON(http.StatusOK, vulnerabilities)
+ // 计算总页数
+ totalPages := (total + limit - 1) / limit
+ if totalPages == 0 {
+ totalPages = 1
+ }
+
+ // 如果使用offset计算page,需要重新计算
+ if pageStr == "" {
+ page = (offset / limit) + 1
+ }
+
+ response := ListVulnerabilitiesResponse{
+ Vulnerabilities: vulnerabilities,
+ Total: total,
+ Page: page,
+ PageSize: limit,
+ TotalPages: totalPages,
+ }
+
+ c.JSON(http.StatusOK, response)
}
// UpdateVulnerabilityRequest 更新漏洞请求
diff --git a/web/static/css/style.css b/web/static/css/style.css
index 2b0834e7..81705980 100644
--- a/web/static/css/style.css
+++ b/web/static/css/style.css
@@ -2951,7 +2951,8 @@ header {
}
.tools-pagination,
-.monitor-pagination {
+.monitor-pagination,
+.pagination {
display: flex;
justify-content: space-between;
align-items: center;
@@ -2980,11 +2981,51 @@ header {
min-width: auto;
}
-.pagination-controls button:disabled {
+.pagination-controls button:disabled,
+.pagination-btn.disabled {
opacity: 0.5;
cursor: not-allowed;
}
+.pagination-btn {
+ padding: 6px 12px;
+ font-size: 0.875rem;
+ min-width: 36px;
+ height: 32px;
+ border: 1px solid var(--border-color);
+ border-radius: 6px;
+ background: var(--bg-primary);
+ color: var(--text-primary);
+ cursor: pointer;
+ transition: all 0.2s;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.pagination-btn:hover:not(.disabled):not(.active) {
+ background: var(--bg-tertiary);
+ border-color: var(--accent-color);
+}
+
+.pagination-btn.active {
+ background: var(--accent-color);
+ color: white;
+ border-color: var(--accent-color);
+ font-weight: 500;
+}
+
+.pagination-btn.disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+.pagination-ellipsis {
+ padding: 0 8px;
+ color: var(--text-secondary);
+ font-size: 0.875rem;
+}
+
.pagination-page {
font-size: 0.875rem;
color: var(--text-secondary);
diff --git a/web/static/js/vulnerability.js b/web/static/js/vulnerability.js
index 94b21b01..c75473b3 100644
--- a/web/static/js/vulnerability.js
+++ b/web/static/js/vulnerability.js
@@ -1,5 +1,11 @@
// 漏洞管理相关功能
+// 从localStorage读取每页显示数量,默认为20
+const getVulnerabilityPageSize = () => {
+ const saved = localStorage.getItem('vulnerabilityPageSize');
+ return saved ? parseInt(saved, 10) : 20;
+};
+
let currentVulnerabilityId = null;
let vulnerabilityFilters = {
id: '',
@@ -7,9 +13,17 @@ let vulnerabilityFilters = {
severity: '',
status: ''
};
+let vulnerabilityPagination = {
+ currentPage: 1,
+ pageSize: getVulnerabilityPageSize(),
+ total: 0,
+ totalPages: 1
+};
// 初始化漏洞管理页面
function initVulnerabilityPage() {
+ // 从localStorage加载每页条数设置
+ vulnerabilityPagination.pageSize = getVulnerabilityPageSize();
loadVulnerabilityStats();
loadVulnerabilities();
}
@@ -66,7 +80,7 @@ function updateVulnerabilityStats(stats) {
}
// 加载漏洞列表
-async function loadVulnerabilities() {
+async function loadVulnerabilities(page = null) {
const listContainer = document.getElementById('vulnerabilities-list');
listContainer.innerHTML = '
加载中...
';
@@ -77,9 +91,14 @@ async function loadVulnerabilities() {
throw new Error('apiFetch未定义');
}
+ // 如果指定了页码,使用页码;否则使用当前页码
+ if (page !== null) {
+ vulnerabilityPagination.currentPage = page;
+ }
+
const params = new URLSearchParams();
- params.append('limit', '100');
- params.append('offset', '0');
+ params.append('page', vulnerabilityPagination.currentPage.toString());
+ params.append('limit', vulnerabilityPagination.pageSize.toString());
if (vulnerabilityFilters.id) {
params.append('id', vulnerabilityFilters.id);
@@ -101,8 +120,32 @@ async function loadVulnerabilities() {
throw new Error(`获取漏洞列表失败: ${response.status}`);
}
- const vulnerabilities = await response.json();
+ const data = await response.json();
+
+ // 判断响应格式:新格式(有total字段)还是旧格式(直接是数组)
+ let vulnerabilities;
+ if (Array.isArray(data)) {
+ // 旧格式:直接是数组
+ vulnerabilities = data;
+ // 使用数组长度作为总数(可能不准确,但至少能显示分页控件)
+ vulnerabilityPagination.total = data.length;
+ vulnerabilityPagination.totalPages = Math.max(1, Math.ceil(data.length / vulnerabilityPagination.pageSize));
+ console.warn('后端返回的是旧格式(数组),建议更新后端API以支持分页');
+ } else if (data.vulnerabilities) {
+ // 新格式:包含分页信息的对象
+ vulnerabilities = data.vulnerabilities;
+ vulnerabilityPagination.total = data.total || 0;
+ vulnerabilityPagination.currentPage = data.page || vulnerabilityPagination.currentPage;
+ vulnerabilityPagination.pageSize = data.page_size || vulnerabilityPagination.pageSize;
+ vulnerabilityPagination.totalPages = data.total_pages || 1;
+ } else {
+ // 未知格式,尝试作为数组处理
+ vulnerabilities = [];
+ console.error('未知的响应格式:', data);
+ }
+
renderVulnerabilities(vulnerabilities);
+ renderVulnerabilityPagination();
} catch (error) {
console.error('加载漏洞列表失败:', error);
listContainer.innerHTML = `加载失败: ${error.message}
`;
@@ -121,6 +164,11 @@ function renderVulnerabilities(vulnerabilities) {
if (vulnerabilities.length === 0) {
listContainer.innerHTML = '暂无漏洞记录
';
+ // 清空分页信息
+ const paginationContainer = document.getElementById('vulnerability-pagination');
+ if (paginationContainer) {
+ paginationContainer.innerHTML = '';
+ }
return;
}
@@ -160,6 +208,13 @@ function renderVulnerabilities(vulnerabilities) {
+