Add files via upload

This commit is contained in:
公明
2026-01-16 19:26:52 +08:00
committed by GitHub
parent 15a713743f
commit 18fa0ad9e7
5 changed files with 184 additions and 127 deletions
+12
View File
@@ -349,6 +349,18 @@ func New(cfg *config.Config, log *logger.Logger) (*App, error) {
}
configHandler.SetVulnerabilityToolRegistrar(vulnerabilityRegistrar)
// 设置Skills工具注册器(内置工具,必须设置)
skillsRegistrar := func() error {
// 创建一个适配器,将database.DB适配为SkillStatsStorage接口
var skillStatsStorage skills.SkillStatsStorage
if db != nil {
skillStatsStorage = &skillStatsDBAdapter{db: db}
}
skills.RegisterSkillsToolWithStorage(mcpServer, skillsManager, skillStatsStorage, log.Logger)
return nil
}
configHandler.SetSkillsToolRegistrar(skillsRegistrar)
// 设置知识库初始化器(用于动态初始化,需要在 App 创建后设置)
configHandler.SetKnowledgeInitializer(func() (*handler.KnowledgeHandler, error) {
knowledgeHandler, err := initializeKnowledge(cfg, db, knowledgeDBConn, mcpServer, agentHandler, app, log.Logger)
+21
View File
@@ -28,6 +28,9 @@ type KnowledgeToolRegistrar func() error
// VulnerabilityToolRegistrar 漏洞工具注册器接口
type VulnerabilityToolRegistrar func() error
// SkillsToolRegistrar Skills工具注册器接口
type SkillsToolRegistrar func() error
// RetrieverUpdater 检索器更新接口
type RetrieverUpdater interface {
UpdateConfig(config *knowledge.RetrievalConfig)
@@ -52,6 +55,7 @@ type ConfigHandler struct {
externalMCPMgr *mcp.ExternalMCPManager // 外部MCP管理器
knowledgeToolRegistrar KnowledgeToolRegistrar // 知识库工具注册器(可选)
vulnerabilityToolRegistrar VulnerabilityToolRegistrar // 漏洞工具注册器(可选)
skillsToolRegistrar SkillsToolRegistrar // Skills工具注册器(可选)
retrieverUpdater RetrieverUpdater // 检索器更新器(可选)
knowledgeInitializer KnowledgeInitializer // 知识库初始化器(可选)
appUpdater AppUpdater // App更新器(可选)
@@ -110,6 +114,13 @@ func (h *ConfigHandler) SetVulnerabilityToolRegistrar(registrar VulnerabilityToo
h.vulnerabilityToolRegistrar = registrar
}
// SetSkillsToolRegistrar 设置Skills工具注册器
func (h *ConfigHandler) SetSkillsToolRegistrar(registrar SkillsToolRegistrar) {
h.mu.Lock()
defer h.mu.Unlock()
h.skillsToolRegistrar = registrar
}
// SetRetrieverUpdater 设置检索器更新器
func (h *ConfigHandler) SetRetrieverUpdater(updater RetrieverUpdater) {
h.mu.Lock()
@@ -869,6 +880,16 @@ func (h *ConfigHandler) ApplyConfig(c *gin.Context) {
}
}
// 重新注册Skills工具(内置工具,必须注册)
if h.skillsToolRegistrar != nil {
h.logger.Info("重新注册Skills工具")
if err := h.skillsToolRegistrar(); err != nil {
h.logger.Error("重新注册Skills工具失败", zap.Error(err))
} else {
h.logger.Info("Skills工具已重新注册")
}
}
// 如果知识库启用,重新注册知识库工具
if h.config.Knowledge.Enabled && h.knowledgeToolRegistrar != nil {
h.logger.Info("重新注册知识库工具")
+88 -48
View File
@@ -398,7 +398,7 @@ body {
}
.page-header {
padding: 20px 24px;
padding: 12px 20px;
background: linear-gradient(135deg, #ffffff 0%, #fafbfc 100%);
border-bottom: 1px solid var(--border-color);
display: flex;
@@ -410,14 +410,14 @@ body {
.page-header h2 {
margin: 0;
font-size: 1.5rem;
font-size: 1.25rem;
font-weight: 600;
color: var(--text-primary);
}
.page-header-actions {
display: flex;
gap: 12px;
gap: 8px;
align-items: center;
}
@@ -425,10 +425,18 @@ body {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
padding: 24px;
padding: 16px 20px;
min-height: 0;
}
/* 任务管理页面内容区域底部圆角 */
#page-tasks .page-content {
/* 确保底部左右角都是圆角 */
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
overflow: hidden;
}
/* 对话页面布局 */
.chat-page-layout {
display: flex;
@@ -3150,18 +3158,20 @@ header {
}
.btn-search {
padding: 8px 16px;
padding: 6px 12px;
border: 1px solid var(--border-color);
border-radius: 6px;
background: var(--bg-primary);
color: var(--text-primary);
font-size: 1rem;
font-size: 0.875rem;
cursor: pointer;
transition: all 0.2s;
min-width: 40px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
}
.btn-search:hover {
@@ -3263,7 +3273,7 @@ header {
}
.pagination-container {
margin-top: 16px;
margin-top: 10px;
}
/* Skills管理页面分页优化 */
@@ -3297,16 +3307,19 @@ header {
position: relative;
/* 添加四个角的圆角,与上方卡片保持一致 */
border-radius: 8px;
/* 确保底部左右角都是圆角 */
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
}
.pagination-fixed .pagination {
margin-top: 0;
border-top: 1px solid var(--border-color);
padding: 16px 20px;
padding: 10px 16px;
background: var(--bg-primary);
justify-content: space-between;
align-items: center;
gap: 20px;
gap: 12px;
flex-wrap: wrap;
/* 确保分页内容与列表内容对齐 */
width: 100%;
@@ -3315,15 +3328,18 @@ header {
border-top-color: rgba(233, 236, 239, 0.6);
/* 添加四个角的圆角,与上方卡片保持一致 */
border-radius: 8px;
/* 确保底部左右角都是圆角 */
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
}
/* 左侧:信息显示和每页数量选择器 - 更自然的设计 */
.pagination-fixed .pagination-info {
font-size: 0.875rem;
font-size: 0.8125rem;
color: var(--text-secondary);
display: flex;
align-items: center;
gap: 20px;
gap: 12px;
flex-wrap: wrap;
/* 去掉背景色和边框,更自然 */
padding: 0;
@@ -3340,25 +3356,27 @@ header {
.pagination-fixed .pagination-info .pagination-page-size {
display: flex;
align-items: center;
gap: 8px;
font-size: 0.875rem;
gap: 6px;
font-size: 0.8125rem;
color: var(--text-secondary);
font-weight: 400;
}
.pagination-fixed .pagination-info .pagination-page-size select {
padding: 6px 10px;
padding: 4px 8px;
border-radius: 6px;
border: 1px solid var(--border-color);
background: var(--bg-primary);
color: var(--text-primary);
font-size: 0.875rem;
font-size: 0.8125rem;
cursor: pointer;
transition: all 0.2s ease;
min-width: 70px;
min-width: 60px;
font-weight: 500;
/* 更柔和的边框 */
border-color: rgba(233, 236, 239, 0.8);
/* 确保四个角都是圆角 */
border-radius: 8px !important;
}
.pagination-fixed .pagination-info .pagination-page-size select:focus {
@@ -3382,14 +3400,16 @@ header {
}
.pagination-fixed .pagination-controls .btn-secondary {
padding: 7px 14px;
font-size: 0.875rem;
padding: 5px 12px;
font-size: 0.8125rem;
min-width: auto;
transition: all 0.2s ease;
font-weight: 500;
border-radius: 8px;
border-radius: 6px;
/* 更柔和的边框 */
border-color: rgba(233, 236, 239, 0.8);
/* 确保四个角都是圆角 */
border-radius: 8px !important;
}
.pagination-fixed .pagination-controls .btn-secondary:hover:not(:disabled) {
@@ -3414,11 +3434,18 @@ header {
}
.pagination-fixed .pagination-page {
font-size: 0.875rem;
font-size: 0.8125rem;
color: var(--text-secondary);
padding: 0 12px;
padding: 5px 10px;
white-space: nowrap;
font-weight: 400;
/* 添加圆角设计,四个角都是圆的 */
border-radius: 6px !important;
background: var(--bg-secondary) !important;
border: 1px solid var(--border-color) !important;
display: inline-flex !important;
align-items: center;
justify-content: center;
}
/* 响应式优化 */
@@ -3598,12 +3625,12 @@ header {
}
.btn-primary {
padding: 10px 20px;
padding: 7px 16px;
background: var(--accent-color);
color: white;
border: none;
border-radius: 6px;
font-size: 0.9375rem;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
@@ -3616,12 +3643,12 @@ header {
}
.btn-secondary {
padding: 10px 20px;
padding: 7px 16px;
background: var(--bg-secondary);
color: var(--text-primary);
border: 1px solid var(--border-color);
border-radius: 6px;
font-size: 0.9375rem;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
@@ -6865,9 +6892,10 @@ header {
align-items: flex-end;
flex-wrap: wrap;
padding: 16px;
background: var(--bg-secondary);
background: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: 12px;
margin-bottom: 16px;
}
.tasks-filters label {
@@ -7260,8 +7288,12 @@ header {
/* 批量任务相关样式 */
.batch-queues-section {
margin-top: 16px;
padding-top: 8px;
margin-top: 0;
padding-top: 0;
/* 确保底部左右角都是圆角 */
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
overflow: hidden;
}
.batch-queues-header {
@@ -7283,6 +7315,7 @@ header {
display: flex;
flex-direction: column;
gap: 16px;
margin-bottom: 16px;
}
.batch-queue-item {
@@ -9218,17 +9251,17 @@ header {
/* Skills管理页面样式 */
.skills-controls {
margin-bottom: 24px;
margin-bottom: 16px;
}
.skills-stats-bar {
display: flex;
gap: 16px;
padding: 16px;
gap: 12px;
padding: 10px 12px;
background: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: 8px;
margin-bottom: 16px;
border-radius: 6px;
margin-bottom: 10px;
}
.skill-stat-item {
@@ -9238,41 +9271,45 @@ header {
}
.skill-stat-label {
font-size: 0.8125rem;
font-size: 0.75rem;
color: var(--text-secondary);
}
.skill-stat-value {
font-size: 1.25rem;
font-size: 1.125rem;
font-weight: 600;
color: var(--text-primary);
}
.skills-filters {
display: flex;
gap: 12px;
gap: 8px;
align-items: center;
}
.skills-filters input {
flex: 1;
padding: 8px 12px;
padding: 6px 10px;
border: 1px solid var(--border-color);
border-radius: 6px;
font-size: 0.875rem;
box-sizing: border-box;
height: 32px;
line-height: 1.4;
}
.skills-list {
display: flex;
flex-direction: column;
gap: 12px;
gap: 16px;
margin-bottom: 16px;
}
.skill-item {
background: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 16px;
border-radius: 6px;
padding: 10px 12px;
transition: all 0.2s;
}
@@ -9285,7 +9322,7 @@ header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 12px;
margin-bottom: 6px;
}
.skill-item-info {
@@ -9293,28 +9330,30 @@ header {
}
.skill-item-name {
font-size: 1.125rem;
font-size: 1rem;
font-weight: 600;
color: var(--text-primary);
margin: 0 0 8px 0;
margin: 0 0 4px 0;
line-height: 1.3;
}
.skill-item-desc {
font-size: 0.875rem;
font-size: 0.8125rem;
color: var(--text-secondary);
margin: 0;
line-height: 1.4;
}
.skill-item-actions {
display: flex;
gap: 8px;
gap: 6px;
}
.btn-icon {
background: none;
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 6px;
padding: 4px;
cursor: pointer;
display: flex;
align-items: center;
@@ -9337,10 +9376,11 @@ header {
.skill-item-meta {
display: flex;
gap: 16px;
gap: 12px;
flex-wrap: wrap;
font-size: 0.8125rem;
font-size: 0.75rem;
color: var(--text-secondary);
line-height: 1.4;
}
.skill-meta-item {
+62 -78
View File
@@ -1051,106 +1051,90 @@ function renderBatchQueues() {
renderBatchQueuesPagination();
}
// 渲染批量任务队列分页控件
// 渲染批量任务队列分页控件(参考Skills管理页面样式)
function renderBatchQueuesPagination() {
const paginationContainer = document.getElementById('batch-queues-pagination');
if (!paginationContainer) return;
const { currentPage, pageSize, total, totalPages } = batchQueuesState;
// 如果没有数据,不显示分页控件
// 即使只有一页也显示分页信息(参考Skills样式)
if (total === 0) {
paginationContainer.innerHTML = '';
paginationContainer.style.display = 'none';
return;
}
// 确保分页控件可见
paginationContainer.style.display = '';
// 即使只有一页,也显示分页信息(总数和每页条数选择器)
// 计算显示的页码范围
let startPage = Math.max(1, currentPage - 2);
let endPage = Math.min(totalPages, currentPage + 2);
// 确保显示5个页码(如果可能)
if (endPage - startPage < 4) {
if (startPage === 1) {
endPage = Math.min(totalPages, startPage + 4);
} else if (endPage === totalPages) {
startPage = Math.max(1, endPage - 4);
}
}
// 计算显示范围
const start = total === 0 ? 0 : (currentPage - 1) * pageSize + 1;
const end = total === 0 ? 0 : Math.min(currentPage * pageSize, total);
let paginationHTML = '<div class="pagination">';
const startItem = (currentPage - 1) * pageSize + 1;
const endItem = Math.min(currentPage * pageSize, total);
paginationHTML += `<div class="pagination-info">显示 ${startItem}-${endItem} / 共 ${total} 条</div>`;
// 每页条数选择器
// 左侧:显示范围信息和每页数量选择器(参考Skills样式)
paginationHTML += `
<div class="pagination-page-size">
<label for="batch-queues-page-size-pagination">每页:</label>
<select id="batch-queues-page-size-pagination" onchange="changeBatchQueuesPageSize()">
<option value="10" ${pageSize === 10 ? 'selected' : ''}>10</option>
<option value="20" ${pageSize === 20 ? 'selected' : ''}>20</option>
<option value="50" ${pageSize === 50 ? 'selected' : ''}>50</option>
<option value="100" ${pageSize === 100 ? 'selected' : ''}>100</option>
</select>
<div class="pagination-info">
<span>显示 ${start}-${end} / ${total} </span>
<label class="pagination-page-size">
每页显示
<select id="batch-queues-page-size-pagination" onchange="changeBatchQueuesPageSize()">
<option value="10" ${pageSize === 10 ? 'selected' : ''}>10</option>
<option value="20" ${pageSize === 20 ? 'selected' : ''}>20</option>
<option value="50" ${pageSize === 50 ? 'selected' : ''}>50</option>
<option value="100" ${pageSize === 100 ? 'selected' : ''}>100</option>
</select>
</label>
</div>
`;
// 只有当有多页时才显示页码导航
if (totalPages > 1) {
paginationHTML += '<div class="pagination-controls">';
// 上一页按钮
if (currentPage > 1) {
paginationHTML += `<button class="pagination-btn" onclick="goBatchQueuesPage(${currentPage - 1})" title="上一页"></button>`;
} else {
paginationHTML += '<button class="pagination-btn disabled" disabled></button>';
}
// 第一页
if (startPage > 1) {
paginationHTML += `<button class="pagination-btn" onclick="goBatchQueuesPage(1)">1</button>`;
if (startPage > 2) {
paginationHTML += '<span class="pagination-ellipsis">...</span>';
}
}
// 页码按钮
for (let i = startPage; i <= endPage; i++) {
if (i === currentPage) {
paginationHTML += `<button class="pagination-btn active">${i}</button>`;
} else {
paginationHTML += `<button class="pagination-btn" onclick="goBatchQueuesPage(${i})">${i}</button>`;
}
}
// 最后一页
if (endPage < totalPages) {
if (endPage < totalPages - 1) {
paginationHTML += '<span class="pagination-ellipsis">...</span>';
}
paginationHTML += `<button class="pagination-btn" onclick="goBatchQueuesPage(${totalPages})">${totalPages}</button>`;
}
// 下一页按钮
if (currentPage < totalPages) {
paginationHTML += `<button class="pagination-btn" onclick="goBatchQueuesPage(${currentPage + 1})" title="下一页"></button>`;
} else {
paginationHTML += '<button class="pagination-btn disabled" disabled></button>';
}
paginationHTML += '</div>';
}
// 右侧:分页按钮(参考Skills样式:首页、上一页、第X/Y页、下一页、末页)
paginationHTML += `
<div class="pagination-controls">
<button class="btn-secondary" onclick="goBatchQueuesPage(1)" ${currentPage === 1 || total === 0 ? 'disabled' : ''}>首页</button>
<button class="btn-secondary" onclick="goBatchQueuesPage(${currentPage - 1})" ${currentPage === 1 || total === 0 ? 'disabled' : ''}>上一页</button>
<span class="pagination-page"> ${currentPage} / ${totalPages || 1} </span>
<button class="btn-secondary" onclick="goBatchQueuesPage(${currentPage + 1})" ${currentPage >= totalPages || total === 0 ? 'disabled' : ''}>下一页</button>
<button class="btn-secondary" onclick="goBatchQueuesPage(${totalPages || 1})" ${currentPage >= totalPages || total === 0 ? 'disabled' : ''}>末页</button>
</div>
`;
paginationHTML += '</div>';
paginationContainer.innerHTML = paginationHTML;
// 确保分页组件与列表内容区域对齐(不包括滚动条)
function alignPaginationWidth() {
const batchQueuesList = document.getElementById('batch-queues-list');
if (batchQueuesList && paginationContainer) {
// 获取列表的实际内容宽度(不包括滚动条)
const listClientWidth = batchQueuesList.clientWidth; // 可视区域宽度(不包括滚动条)
const listScrollHeight = batchQueuesList.scrollHeight; // 内容总高度
const listClientHeight = batchQueuesList.clientHeight; // 可视区域高度
const hasScrollbar = listScrollHeight > listClientHeight;
// 如果列表有垂直滚动条,分页组件应该与列表内容区域对齐(clientWidth
// 如果没有滚动条,使用100%宽度
if (hasScrollbar) {
// 分页组件应该与列表内容区域对齐,不包括滚动条
paginationContainer.style.width = `${listClientWidth}px`;
} else {
// 如果没有滚动条,使用100%宽度
paginationContainer.style.width = '100%';
}
}
}
// 立即执行一次
alignPaginationWidth();
// 监听窗口大小变化和列表内容变化
const resizeObserver = new ResizeObserver(() => {
alignPaginationWidth();
});
const batchQueuesList = document.getElementById('batch-queues-list');
if (batchQueuesList) {
resizeObserver.observe(batchQueuesList);
}
}
// 跳转到指定页面
+1 -1
View File
@@ -665,7 +665,7 @@
</div>
<div id="batch-queues-list" class="batch-queues-list"></div>
<!-- 分页控件 -->
<div id="batch-queues-pagination"></div>
<div id="batch-queues-pagination" class="pagination-container pagination-fixed"></div>
</div>
</div>
</div>