mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-08-20 18:08:05 +02:00
Add files via upload
This commit is contained in:
@@ -1,11 +1,16 @@
|
|||||||
# CyberStrikeAI
|
# CyberStrikeAI
|
||||||
|
|
||||||
🚀 **AI自主渗透测试平台** - 基于Golang构建,内置上百个安全工具,支持灵活扩展自定义工具,通过MCP协议实现AI智能决策与自动化执行,让安全测试像对话一样简单。(目前该工具也已支持 MCP Stdio 模式)
|
🚀 **AI自主渗透测试平台** - 基于Golang构建,内置上百个安全工具,支持灵活扩展自定义工具,通过MCP协议实现AI智能决策与自动化执行,让安全测试像对话一样简单。
|
||||||

|

|
||||||
|
|
||||||
## 更新日志
|
## 更新日志
|
||||||
- 2025.11.13 ✨ 新增 MCP Stdio 模式支持,现可在代码编辑器、CLI 及自动化脚本等多种场景下,无缝集成并使用全套安全工具;
|
- 2025.11.13 新增 MCP stdio 模式支持,可在 Cursor IDE 中直接使用所有安全工具;
|
||||||
- 2025.11.12 ✨ 新增 任务停止功能,优化前端;
|
- 2025.11.12 增加了任务停止功能,优化前端;
|
||||||
|
- 2025.01.XX 优化系统设置功能:
|
||||||
|
- ✅ 打开设置时自动加载当前 `config.yaml` 中的配置
|
||||||
|
- ✅ 关键配置项(API Key、Base URL、模型)设置为必填,保存时进行验证
|
||||||
|
- ✅ 优化模态框圆角显示,修复视觉问题
|
||||||
|
- ✅ 改进配置表单验证,提供清晰的错误提示
|
||||||
|
|
||||||
## ✨ 功能特性
|
## ✨ 功能特性
|
||||||
|
|
||||||
@@ -17,6 +22,7 @@
|
|||||||
- 📝 **智能总结** - 达到最大迭代次数时,AI自动总结测试结果并提供下一步执行计划
|
- 📝 **智能总结** - 达到最大迭代次数时,AI自动总结测试结果并提供下一步执行计划
|
||||||
- 💬 **对话式交互** - 自然语言对话界面,支持流式输出(SSE),实时查看执行过程
|
- 💬 **对话式交互** - 自然语言对话界面,支持流式输出(SSE),实时查看执行过程
|
||||||
- 📊 **对话历史管理** - 完整的对话历史记录,支持查看、删除和管理
|
- 📊 **对话历史管理** - 完整的对话历史记录,支持查看、删除和管理
|
||||||
|
- ⚙️ **可视化配置管理** - Web界面配置系统设置,支持实时加载和保存配置,必填项验证
|
||||||
|
|
||||||
### 工具集成
|
### 工具集成
|
||||||
- 🔌 **MCP协议支持** - 完整实现MCP协议,支持工具注册、调用、监控
|
- 🔌 **MCP协议支持** - 完整实现MCP协议,支持工具注册、调用、监控
|
||||||
@@ -109,6 +115,18 @@ go mod download
|
|||||||
```
|
```
|
||||||
|
|
||||||
3. **配置**
|
3. **配置**
|
||||||
|
|
||||||
|
#### 方式一:通过Web界面配置(推荐)
|
||||||
|
|
||||||
|
启动服务器后,在Web界面点击右上角的"设置"按钮,可以可视化配置:
|
||||||
|
- **OpenAI配置**:API Key、Base URL、模型(必填项,标记为 *)
|
||||||
|
- **MCP工具配置**:启用/禁用工具
|
||||||
|
- **Agent配置**:最大迭代次数等
|
||||||
|
|
||||||
|
配置会自动保存到 `config.yaml` 文件中。打开设置时会自动加载当前配置文件中的值。
|
||||||
|
|
||||||
|
#### 方式二:直接编辑配置文件
|
||||||
|
|
||||||
编辑 `config.yaml` 文件,设置您的API配置:
|
编辑 `config.yaml` 文件,设置您的API配置:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -137,6 +155,8 @@ security:
|
|||||||
- DeepSeek: `https://api.deepseek.com/v1`
|
- DeepSeek: `https://api.deepseek.com/v1`
|
||||||
- 其他兼容OpenAI协议的API服务
|
- 其他兼容OpenAI协议的API服务
|
||||||
|
|
||||||
|
**注意**:API Key、Base URL 和模型是必填项,必须配置才能正常运行系统。在Web界面配置时,这些字段会进行验证,未填写时会显示错误提示。
|
||||||
|
|
||||||
4. **安装安全工具(可选)**
|
4. **安装安全工具(可选)**
|
||||||
|
|
||||||
根据您的需求安装相应的安全工具。系统支持上百个工具,您可以根据实际需要选择性安装:
|
根据您的需求安装相应的安全工具。系统支持上百个工具,您可以根据实际需要选择性安装:
|
||||||
@@ -187,9 +207,33 @@ go run cmd/server/main.go -config /path/to/config.yaml
|
|||||||
- **对话测试** - 与AI对话进行渗透测试
|
- **对话测试** - 与AI对话进行渗透测试
|
||||||
- **工具监控** - 查看工具执行状态和结果
|
- **工具监控** - 查看工具执行状态和结果
|
||||||
- **对话历史** - 管理历史对话记录
|
- **对话历史** - 管理历史对话记录
|
||||||
|
- **系统设置** - 配置API密钥、工具启用状态等(点击右上角设置按钮)
|
||||||
|
|
||||||
|
**首次使用提示**:
|
||||||
|
- 在开始使用前,请先点击右上角的"设置"按钮配置您的API Key
|
||||||
|
- API Key、Base URL 和模型是必填项(标记为 *),必须填写才能正常使用
|
||||||
|
- 配置会自动保存到 `config.yaml` 文件中
|
||||||
|
- 打开设置时会自动加载当前配置文件中的最新配置
|
||||||
|
|
||||||
## ⚙️ 配置说明
|
## ⚙️ 配置说明
|
||||||
|
|
||||||
|
### Web界面配置管理
|
||||||
|
|
||||||
|
系统提供了可视化的配置管理界面,您可以通过以下方式访问:
|
||||||
|
|
||||||
|
1. **打开设置**:点击Web界面右上角的"设置"按钮
|
||||||
|
2. **加载配置**:打开设置时会自动从 `config.yaml` 加载当前配置
|
||||||
|
3. **修改配置**:
|
||||||
|
- **OpenAI配置**:修改API Key、Base URL、模型(必填项标记为 *)
|
||||||
|
- **MCP工具配置**:启用或禁用工具,支持搜索和批量操作
|
||||||
|
- **Agent配置**:设置最大迭代次数等参数
|
||||||
|
4. **保存配置**:点击"应用配置"按钮,配置会保存到 `config.yaml` 并立即生效
|
||||||
|
5. **验证提示**:必填项未填写时会显示错误提示,并高亮显示错误字段
|
||||||
|
|
||||||
|
**配置验证规则**:
|
||||||
|
- API Key、Base URL、模型为必填项
|
||||||
|
- 保存时会自动验证,未填写必填项会阻止保存并提示错误
|
||||||
|
|
||||||
### 完整配置示例
|
### 完整配置示例
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
agent:
|
||||||
|
max_iterations: 30
|
||||||
|
database:
|
||||||
|
path: data/conversations.db
|
||||||
|
log:
|
||||||
|
level: info
|
||||||
|
output: stdout
|
||||||
|
mcp:
|
||||||
|
enabled: true
|
||||||
|
host: 0.0.0.0
|
||||||
|
port: 8081
|
||||||
|
openai:
|
||||||
|
api_key: sk-f02ac0f7cd114ff3996e1466455ebba9
|
||||||
|
base_url: https://api.deepseek.com/v1
|
||||||
|
model: deepseek-chat
|
||||||
|
security:
|
||||||
|
tools_dir: tools
|
||||||
|
server:
|
||||||
|
host: 0.0.0.0
|
||||||
|
port: 8080
|
||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"cyberstrike-ai/internal/config"
|
"cyberstrike-ai/internal/config"
|
||||||
@@ -23,6 +24,7 @@ type Agent struct {
|
|||||||
mcpServer *mcp.Server
|
mcpServer *mcp.Server
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
maxIterations int
|
maxIterations int
|
||||||
|
mu sync.RWMutex // 添加互斥锁以支持并发更新
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAgent 创建新的Agent
|
// NewAgent 创建新的Agent
|
||||||
@@ -938,6 +940,27 @@ func (a *Agent) executeToolViaMCP(ctx context.Context, toolName string, args map
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateConfig 更新OpenAI配置
|
||||||
|
func (a *Agent) UpdateConfig(cfg *config.OpenAIConfig) {
|
||||||
|
a.mu.Lock()
|
||||||
|
defer a.mu.Unlock()
|
||||||
|
a.config = cfg
|
||||||
|
a.logger.Info("Agent配置已更新",
|
||||||
|
zap.String("base_url", cfg.BaseURL),
|
||||||
|
zap.String("model", cfg.Model),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateMaxIterations 更新最大迭代次数
|
||||||
|
func (a *Agent) UpdateMaxIterations(maxIterations int) {
|
||||||
|
a.mu.Lock()
|
||||||
|
defer a.mu.Unlock()
|
||||||
|
if maxIterations > 0 {
|
||||||
|
a.maxIterations = maxIterations
|
||||||
|
a.logger.Info("Agent最大迭代次数已更新", zap.Int("max_iterations", maxIterations))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// formatToolError 格式化工具错误信息,提供更友好的错误描述
|
// formatToolError 格式化工具错误信息,提供更友好的错误描述
|
||||||
func (a *Agent) formatToolError(toolName string, args map[string]interface{}, err error) string {
|
func (a *Agent) formatToolError(toolName string, args map[string]interface{}, err error) string {
|
||||||
errorMsg := fmt.Sprintf(`工具执行失败
|
errorMsg := fmt.Sprintf(`工具执行失败
|
||||||
|
|||||||
+14
-2
@@ -74,8 +74,15 @@ func New(cfg *config.Config, log *logger.Logger) (*App, error) {
|
|||||||
monitorHandler := handler.NewMonitorHandler(mcpServer, executor, log.Logger)
|
monitorHandler := handler.NewMonitorHandler(mcpServer, executor, log.Logger)
|
||||||
conversationHandler := handler.NewConversationHandler(db, log.Logger)
|
conversationHandler := handler.NewConversationHandler(db, log.Logger)
|
||||||
|
|
||||||
|
// 获取配置文件路径
|
||||||
|
configPath := "config.yaml"
|
||||||
|
if len(os.Args) > 1 {
|
||||||
|
configPath = os.Args[1]
|
||||||
|
}
|
||||||
|
configHandler := handler.NewConfigHandler(configPath, cfg, mcpServer, executor, agent, log.Logger)
|
||||||
|
|
||||||
// 设置路由
|
// 设置路由
|
||||||
setupRoutes(router, agentHandler, monitorHandler, conversationHandler, mcpServer)
|
setupRoutes(router, agentHandler, monitorHandler, conversationHandler, configHandler, mcpServer)
|
||||||
|
|
||||||
return &App{
|
return &App{
|
||||||
config: cfg,
|
config: cfg,
|
||||||
@@ -113,7 +120,7 @@ func (a *App) Run() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// setupRoutes 设置路由
|
// setupRoutes 设置路由
|
||||||
func setupRoutes(router *gin.Engine, agentHandler *handler.AgentHandler, monitorHandler *handler.MonitorHandler, conversationHandler *handler.ConversationHandler, mcpServer *mcp.Server) {
|
func setupRoutes(router *gin.Engine, agentHandler *handler.AgentHandler, monitorHandler *handler.MonitorHandler, conversationHandler *handler.ConversationHandler, configHandler *handler.ConfigHandler, mcpServer *mcp.Server) {
|
||||||
// API路由
|
// API路由
|
||||||
api := router.Group("/api")
|
api := router.Group("/api")
|
||||||
{
|
{
|
||||||
@@ -137,6 +144,11 @@ func setupRoutes(router *gin.Engine, agentHandler *handler.AgentHandler, monitor
|
|||||||
api.GET("/monitor/stats", monitorHandler.GetStats)
|
api.GET("/monitor/stats", monitorHandler.GetStats)
|
||||||
api.GET("/monitor/vulnerabilities", monitorHandler.GetVulnerabilities)
|
api.GET("/monitor/vulnerabilities", monitorHandler.GetVulnerabilities)
|
||||||
|
|
||||||
|
// 配置管理
|
||||||
|
api.GET("/config", configHandler.GetConfig)
|
||||||
|
api.PUT("/config", configHandler.UpdateConfig)
|
||||||
|
api.POST("/config/apply", configHandler.ApplyConfig)
|
||||||
|
|
||||||
// MCP端点
|
// MCP端点
|
||||||
api.POST("/mcp", func(c *gin.Context) {
|
api.POST("/mcp", func(c *gin.Context) {
|
||||||
mcpServer.HandleHTTP(c.Writer, c.Request)
|
mcpServer.HandleHTTP(c.Writer, c.Request)
|
||||||
|
|||||||
@@ -36,9 +36,9 @@ type MCPConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type OpenAIConfig struct {
|
type OpenAIConfig struct {
|
||||||
APIKey string `yaml:"api_key"`
|
APIKey string `yaml:"api_key" json:"api_key"`
|
||||||
BaseURL string `yaml:"base_url"`
|
BaseURL string `yaml:"base_url" json:"base_url"`
|
||||||
Model string `yaml:"model"`
|
Model string `yaml:"model" json:"model"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SecurityConfig struct {
|
type SecurityConfig struct {
|
||||||
@@ -51,7 +51,7 @@ type DatabaseConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AgentConfig struct {
|
type AgentConfig struct {
|
||||||
MaxIterations int `yaml:"max_iterations"`
|
MaxIterations int `yaml:"max_iterations" json:"max_iterations"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ToolConfig struct {
|
type ToolConfig struct {
|
||||||
|
|||||||
@@ -0,0 +1,327 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"cyberstrike-ai/internal/config"
|
||||||
|
"cyberstrike-ai/internal/mcp"
|
||||||
|
"cyberstrike-ai/internal/security"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConfigHandler 配置处理器
|
||||||
|
type ConfigHandler struct {
|
||||||
|
configPath string
|
||||||
|
config *config.Config
|
||||||
|
mcpServer *mcp.Server
|
||||||
|
executor *security.Executor
|
||||||
|
agent AgentUpdater // Agent接口,用于更新Agent配置
|
||||||
|
logger *zap.Logger
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// AgentUpdater Agent更新接口
|
||||||
|
type AgentUpdater interface {
|
||||||
|
UpdateConfig(cfg *config.OpenAIConfig)
|
||||||
|
UpdateMaxIterations(maxIterations int)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConfigHandler 创建新的配置处理器
|
||||||
|
func NewConfigHandler(configPath string, cfg *config.Config, mcpServer *mcp.Server, executor *security.Executor, agent AgentUpdater, logger *zap.Logger) *ConfigHandler {
|
||||||
|
return &ConfigHandler{
|
||||||
|
configPath: configPath,
|
||||||
|
config: cfg,
|
||||||
|
mcpServer: mcpServer,
|
||||||
|
executor: executor,
|
||||||
|
agent: agent,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfigResponse 获取配置响应
|
||||||
|
type GetConfigResponse struct {
|
||||||
|
OpenAI config.OpenAIConfig `json:"openai"`
|
||||||
|
MCP config.MCPConfig `json:"mcp"`
|
||||||
|
Tools []ToolConfigInfo `json:"tools"`
|
||||||
|
Agent config.AgentConfig `json:"agent"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToolConfigInfo 工具配置信息
|
||||||
|
type ToolConfigInfo struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfig 获取当前配置
|
||||||
|
func (h *ConfigHandler) GetConfig(c *gin.Context) {
|
||||||
|
h.mu.RLock()
|
||||||
|
defer h.mu.RUnlock()
|
||||||
|
|
||||||
|
// 获取工具列表
|
||||||
|
tools := make([]ToolConfigInfo, 0, len(h.config.Security.Tools))
|
||||||
|
for _, tool := range h.config.Security.Tools {
|
||||||
|
tools = append(tools, ToolConfigInfo{
|
||||||
|
Name: tool.Name,
|
||||||
|
Description: tool.ShortDescription,
|
||||||
|
Enabled: tool.Enabled,
|
||||||
|
})
|
||||||
|
// 如果没有简短描述,使用详细描述的前100个字符
|
||||||
|
if tools[len(tools)-1].Description == "" {
|
||||||
|
desc := tool.Description
|
||||||
|
if len(desc) > 100 {
|
||||||
|
desc = desc[:100] + "..."
|
||||||
|
}
|
||||||
|
tools[len(tools)-1].Description = desc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, GetConfigResponse{
|
||||||
|
OpenAI: h.config.OpenAI,
|
||||||
|
MCP: h.config.MCP,
|
||||||
|
Tools: tools,
|
||||||
|
Agent: h.config.Agent,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateConfigRequest 更新配置请求
|
||||||
|
type UpdateConfigRequest struct {
|
||||||
|
OpenAI *config.OpenAIConfig `json:"openai,omitempty"`
|
||||||
|
MCP *config.MCPConfig `json:"mcp,omitempty"`
|
||||||
|
Tools []ToolEnableStatus `json:"tools,omitempty"`
|
||||||
|
Agent *config.AgentConfig `json:"agent,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToolEnableStatus 工具启用状态
|
||||||
|
type ToolEnableStatus struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateConfig 更新配置
|
||||||
|
func (h *ConfigHandler) UpdateConfig(c *gin.Context) {
|
||||||
|
var req UpdateConfigRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的请求参数: " + err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
|
|
||||||
|
// 更新OpenAI配置
|
||||||
|
if req.OpenAI != nil {
|
||||||
|
h.config.OpenAI = *req.OpenAI
|
||||||
|
h.logger.Info("更新OpenAI配置",
|
||||||
|
zap.String("base_url", h.config.OpenAI.BaseURL),
|
||||||
|
zap.String("model", h.config.OpenAI.Model),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新MCP配置
|
||||||
|
if req.MCP != nil {
|
||||||
|
h.config.MCP = *req.MCP
|
||||||
|
h.logger.Info("更新MCP配置",
|
||||||
|
zap.Bool("enabled", h.config.MCP.Enabled),
|
||||||
|
zap.String("host", h.config.MCP.Host),
|
||||||
|
zap.Int("port", h.config.MCP.Port),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新Agent配置
|
||||||
|
if req.Agent != nil {
|
||||||
|
h.config.Agent = *req.Agent
|
||||||
|
h.logger.Info("更新Agent配置",
|
||||||
|
zap.Int("max_iterations", h.config.Agent.MaxIterations),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新工具启用状态
|
||||||
|
if req.Tools != nil {
|
||||||
|
toolMap := make(map[string]bool)
|
||||||
|
for _, toolStatus := range req.Tools {
|
||||||
|
toolMap[toolStatus.Name] = toolStatus.Enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新配置中的工具状态
|
||||||
|
for i := range h.config.Security.Tools {
|
||||||
|
if enabled, ok := toolMap[h.config.Security.Tools[i].Name]; ok {
|
||||||
|
h.config.Security.Tools[i].Enabled = enabled
|
||||||
|
h.logger.Info("更新工具启用状态",
|
||||||
|
zap.String("tool", h.config.Security.Tools[i].Name),
|
||||||
|
zap.Bool("enabled", enabled),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存配置到文件
|
||||||
|
if err := h.saveConfig(); err != nil {
|
||||||
|
h.logger.Error("保存配置失败", zap.Error(err))
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "保存配置失败: " + err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{"message": "配置已更新"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApplyConfig 应用配置(重新加载并重启相关服务)
|
||||||
|
func (h *ConfigHandler) ApplyConfig(c *gin.Context) {
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
|
|
||||||
|
// 重新注册工具(根据新的启用状态)
|
||||||
|
h.logger.Info("重新注册工具")
|
||||||
|
|
||||||
|
// 清空MCP服务器中的工具
|
||||||
|
h.mcpServer.ClearTools()
|
||||||
|
|
||||||
|
// 重新注册工具
|
||||||
|
h.executor.RegisterTools(h.mcpServer)
|
||||||
|
|
||||||
|
// 更新Agent的OpenAI配置
|
||||||
|
if h.agent != nil {
|
||||||
|
h.agent.UpdateConfig(&h.config.OpenAI)
|
||||||
|
h.agent.UpdateMaxIterations(h.config.Agent.MaxIterations)
|
||||||
|
h.logger.Info("Agent配置已更新")
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("配置已应用",
|
||||||
|
zap.Int("tools_count", len(h.config.Security.Tools)),
|
||||||
|
)
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"message": "配置已应用",
|
||||||
|
"tools_count": len(h.config.Security.Tools),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// saveConfig 保存配置到文件
|
||||||
|
func (h *ConfigHandler) saveConfig() error {
|
||||||
|
// 读取现有配置文件
|
||||||
|
data, err := os.ReadFile(h.configPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("读取配置文件失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析现有配置
|
||||||
|
var existingConfig map[string]interface{}
|
||||||
|
if err := yaml.Unmarshal(data, &existingConfig); err != nil {
|
||||||
|
return fmt.Errorf("解析配置文件失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新配置值
|
||||||
|
if existingConfig["openai"] == nil {
|
||||||
|
existingConfig["openai"] = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
openaiMap := existingConfig["openai"].(map[string]interface{})
|
||||||
|
if h.config.OpenAI.APIKey != "" {
|
||||||
|
openaiMap["api_key"] = h.config.OpenAI.APIKey
|
||||||
|
}
|
||||||
|
if h.config.OpenAI.BaseURL != "" {
|
||||||
|
openaiMap["base_url"] = h.config.OpenAI.BaseURL
|
||||||
|
}
|
||||||
|
if h.config.OpenAI.Model != "" {
|
||||||
|
openaiMap["model"] = h.config.OpenAI.Model
|
||||||
|
}
|
||||||
|
|
||||||
|
if existingConfig["mcp"] == nil {
|
||||||
|
existingConfig["mcp"] = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
mcpMap := existingConfig["mcp"].(map[string]interface{})
|
||||||
|
mcpMap["enabled"] = h.config.MCP.Enabled
|
||||||
|
if h.config.MCP.Host != "" {
|
||||||
|
mcpMap["host"] = h.config.MCP.Host
|
||||||
|
}
|
||||||
|
if h.config.MCP.Port > 0 {
|
||||||
|
mcpMap["port"] = h.config.MCP.Port
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.config.Agent.MaxIterations > 0 {
|
||||||
|
if existingConfig["agent"] == nil {
|
||||||
|
existingConfig["agent"] = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
agentMap := existingConfig["agent"].(map[string]interface{})
|
||||||
|
agentMap["max_iterations"] = h.config.Agent.MaxIterations
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新工具配置文件中的enabled状态
|
||||||
|
if h.config.Security.ToolsDir != "" {
|
||||||
|
configDir := filepath.Dir(h.configPath)
|
||||||
|
toolsDir := h.config.Security.ToolsDir
|
||||||
|
if !filepath.IsAbs(toolsDir) {
|
||||||
|
toolsDir = filepath.Join(configDir, toolsDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tool := range h.config.Security.Tools {
|
||||||
|
toolFile := filepath.Join(toolsDir, tool.Name+".yaml")
|
||||||
|
// 检查文件是否存在
|
||||||
|
if _, err := os.Stat(toolFile); os.IsNotExist(err) {
|
||||||
|
// 尝试.yml扩展名
|
||||||
|
toolFile = filepath.Join(toolsDir, tool.Name+".yml")
|
||||||
|
if _, err := os.Stat(toolFile); os.IsNotExist(err) {
|
||||||
|
h.logger.Warn("工具配置文件不存在", zap.String("tool", tool.Name))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取工具配置文件
|
||||||
|
toolData, err := os.ReadFile(toolFile)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Warn("读取工具配置文件失败", zap.String("tool", tool.Name), zap.Error(err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析工具配置
|
||||||
|
var toolConfig map[string]interface{}
|
||||||
|
if err := yaml.Unmarshal(toolData, &toolConfig); err != nil {
|
||||||
|
h.logger.Warn("解析工具配置文件失败", zap.String("tool", tool.Name), zap.Error(err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新enabled状态
|
||||||
|
toolConfig["enabled"] = tool.Enabled
|
||||||
|
|
||||||
|
// 保存工具配置文件
|
||||||
|
updatedData, err := yaml.Marshal(toolConfig)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Warn("序列化工具配置失败", zap.String("tool", tool.Name), zap.Error(err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.WriteFile(toolFile, updatedData, 0644); err != nil {
|
||||||
|
h.logger.Warn("保存工具配置文件失败", zap.String("tool", tool.Name), zap.Error(err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("更新工具配置", zap.String("tool", tool.Name), zap.Bool("enabled", tool.Enabled))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存主配置文件
|
||||||
|
updatedData, err := yaml.Marshal(existingConfig)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("序列化配置失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建备份
|
||||||
|
backupPath := h.configPath + ".backup"
|
||||||
|
if err := os.WriteFile(backupPath, data, 0644); err != nil {
|
||||||
|
h.logger.Warn("创建配置备份失败", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存新配置
|
||||||
|
if err := os.WriteFile(h.configPath, updatedData, 0644); err != nil {
|
||||||
|
return fmt.Errorf("保存配置文件失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("配置已保存", zap.String("path", h.configPath))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
@@ -66,6 +66,26 @@ func (s *Server) RegisterTool(tool Tool, handler ToolHandler) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClearTools 清空所有工具(用于重新加载配置)
|
||||||
|
func (s *Server) ClearTools() {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
// 清空工具和工具定义
|
||||||
|
s.tools = make(map[string]ToolHandler)
|
||||||
|
s.toolDefs = make(map[string]Tool)
|
||||||
|
|
||||||
|
// 清空工具相关的资源(保留其他资源)
|
||||||
|
newResources := make(map[string]*Resource)
|
||||||
|
for uri, resource := range s.resources {
|
||||||
|
// 保留非工具资源
|
||||||
|
if !strings.HasPrefix(uri, "tool://") {
|
||||||
|
newResources[uri] = resource
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.resources = newResources
|
||||||
|
}
|
||||||
|
|
||||||
// HandleHTTP 处理HTTP请求
|
// HandleHTTP 处理HTTP请求
|
||||||
func (s *Server) HandleHTTP(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) HandleHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
|
|||||||
@@ -84,6 +84,12 @@ header {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.header-subtitle {
|
.header-subtitle {
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
color: rgba(255, 255, 255, 0.7);
|
color: rgba(255, 255, 255, 0.7);
|
||||||
@@ -91,6 +97,28 @@ header {
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.settings-btn {
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
color: white;
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border-color: rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-btn svg {
|
||||||
|
stroke: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
/* 侧边栏样式 */
|
/* 侧边栏样式 */
|
||||||
.sidebar {
|
.sidebar {
|
||||||
width: 280px;
|
width: 280px;
|
||||||
@@ -755,6 +783,7 @@ header {
|
|||||||
box-shadow: var(--shadow-lg);
|
box-shadow: var(--shadow-lg);
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
animation: slideDown 0.3s ease-out;
|
animation: slideDown 0.3s ease-out;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes slideDown {
|
@keyframes slideDown {
|
||||||
@@ -775,6 +804,8 @@ header {
|
|||||||
padding: 20px 24px;
|
padding: 20px 24px;
|
||||||
border-bottom: 1px solid var(--border-color);
|
border-bottom: 1px solid var(--border-color);
|
||||||
background: var(--bg-primary);
|
background: var(--bg-primary);
|
||||||
|
border-top-left-radius: 12px;
|
||||||
|
border-top-right-radius: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-header h2 {
|
.modal-header h2 {
|
||||||
@@ -1302,3 +1333,216 @@ header {
|
|||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
color: var(--error-color);
|
color: var(--error-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 设置模态框样式 */
|
||||||
|
.settings-modal-content {
|
||||||
|
max-width: 800px;
|
||||||
|
max-height: 90vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-body {
|
||||||
|
overflow-y: auto;
|
||||||
|
flex: 1;
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-section {
|
||||||
|
margin-bottom: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-section:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-section h3 {
|
||||||
|
font-size: 1.125rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 2px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input {
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--accent-color);
|
||||||
|
box-shadow: 0 0 0 3px rgba(0, 102, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input.error {
|
||||||
|
border-color: var(--error-color);
|
||||||
|
box-shadow: 0 0 0 3px rgba(220, 53, 69, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input.error:focus {
|
||||||
|
border-color: var(--error-color);
|
||||||
|
box-shadow: 0 0 0 3px rgba(220, 53, 69, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tools-controls {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tools-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tools-actions button {
|
||||||
|
padding: 8px 16px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tools-actions button:hover {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border-color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tools-actions input {
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tools-list {
|
||||||
|
max-height: 400px;
|
||||||
|
overflow-y: auto;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: background 0.2s;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-item:hover {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-item input[type="checkbox"] {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
accent-color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-item-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-item-name {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-item-desc {
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-item.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 16px 24px;
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
flex-shrink: 0;
|
||||||
|
border-bottom-left-radius: 12px;
|
||||||
|
border-bottom-right-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
padding: 10px 20px;
|
||||||
|
background: var(--accent-color);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: var(--accent-hover);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
padding: 10px 20px;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border-color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|||||||
+205
-7
@@ -1002,13 +1002,6 @@ function closeMCPDetail() {
|
|||||||
document.getElementById('mcp-detail-modal').style.display = 'none';
|
document.getElementById('mcp-detail-modal').style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 点击模态框外部关闭
|
|
||||||
window.onclick = function(event) {
|
|
||||||
const modal = document.getElementById('mcp-detail-modal');
|
|
||||||
if (event.target == modal) {
|
|
||||||
closeMCPDetail();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 工具函数
|
// 工具函数
|
||||||
function getStatusText(status) {
|
function getStatusText(status) {
|
||||||
@@ -1353,6 +1346,211 @@ async function cancelActiveTask(conversationId, button) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设置相关功能
|
||||||
|
let currentConfig = null;
|
||||||
|
let allTools = [];
|
||||||
|
|
||||||
|
// 打开设置
|
||||||
|
async function openSettings() {
|
||||||
|
const modal = document.getElementById('settings-modal');
|
||||||
|
modal.style.display = 'block';
|
||||||
|
|
||||||
|
// 每次打开时重新加载最新配置
|
||||||
|
await loadConfig();
|
||||||
|
|
||||||
|
// 清除之前的验证错误状态
|
||||||
|
document.querySelectorAll('.form-group input').forEach(input => {
|
||||||
|
input.classList.remove('error');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭设置
|
||||||
|
function closeSettings() {
|
||||||
|
const modal = document.getElementById('settings-modal');
|
||||||
|
modal.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点击模态框外部关闭
|
||||||
|
window.onclick = function(event) {
|
||||||
|
const settingsModal = document.getElementById('settings-modal');
|
||||||
|
const mcpModal = document.getElementById('mcp-detail-modal');
|
||||||
|
|
||||||
|
if (event.target == settingsModal) {
|
||||||
|
closeSettings();
|
||||||
|
}
|
||||||
|
if (event.target == mcpModal) {
|
||||||
|
closeMCPDetail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载配置
|
||||||
|
async function loadConfig() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/config');
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('获取配置失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
currentConfig = await response.json();
|
||||||
|
|
||||||
|
// 填充OpenAI配置
|
||||||
|
document.getElementById('openai-api-key').value = currentConfig.openai.api_key || '';
|
||||||
|
document.getElementById('openai-base-url').value = currentConfig.openai.base_url || '';
|
||||||
|
document.getElementById('openai-model').value = currentConfig.openai.model || '';
|
||||||
|
|
||||||
|
// 填充Agent配置
|
||||||
|
document.getElementById('agent-max-iterations').value = currentConfig.agent.max_iterations || 30;
|
||||||
|
|
||||||
|
// 填充工具列表
|
||||||
|
allTools = currentConfig.tools || [];
|
||||||
|
renderToolsList();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载配置失败:', error);
|
||||||
|
alert('加载配置失败: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染工具列表
|
||||||
|
function renderToolsList() {
|
||||||
|
const toolsList = document.getElementById('tools-list');
|
||||||
|
toolsList.innerHTML = '';
|
||||||
|
|
||||||
|
allTools.forEach(tool => {
|
||||||
|
const toolItem = document.createElement('div');
|
||||||
|
toolItem.className = 'tool-item';
|
||||||
|
toolItem.dataset.toolName = tool.name; // 保存原始工具名称
|
||||||
|
toolItem.innerHTML = `
|
||||||
|
<input type="checkbox" id="tool-${tool.name}" ${tool.enabled ? 'checked' : ''} />
|
||||||
|
<div class="tool-item-info">
|
||||||
|
<div class="tool-item-name">${escapeHtml(tool.name)}</div>
|
||||||
|
<div class="tool-item-desc">${escapeHtml(tool.description || '无描述')}</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
toolsList.appendChild(toolItem);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 全选工具
|
||||||
|
function selectAllTools() {
|
||||||
|
document.querySelectorAll('#tools-list input[type="checkbox"]').forEach(checkbox => {
|
||||||
|
checkbox.checked = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 全不选工具
|
||||||
|
function deselectAllTools() {
|
||||||
|
document.querySelectorAll('#tools-list input[type="checkbox"]').forEach(checkbox => {
|
||||||
|
checkbox.checked = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 过滤工具
|
||||||
|
function filterTools() {
|
||||||
|
const searchTerm = document.getElementById('tools-search').value.toLowerCase();
|
||||||
|
document.querySelectorAll('.tool-item').forEach(item => {
|
||||||
|
const toolName = (item.dataset.toolName || '').toLowerCase();
|
||||||
|
const toolDesc = item.querySelector('.tool-item-desc').textContent.toLowerCase();
|
||||||
|
if (toolName.includes(searchTerm) || toolDesc.includes(searchTerm)) {
|
||||||
|
item.classList.remove('hidden');
|
||||||
|
} else {
|
||||||
|
item.classList.add('hidden');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用设置
|
||||||
|
async function applySettings() {
|
||||||
|
try {
|
||||||
|
// 清除之前的验证错误状态
|
||||||
|
document.querySelectorAll('.form-group input').forEach(input => {
|
||||||
|
input.classList.remove('error');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 验证必填字段
|
||||||
|
const apiKey = document.getElementById('openai-api-key').value.trim();
|
||||||
|
const baseUrl = document.getElementById('openai-base-url').value.trim();
|
||||||
|
const model = document.getElementById('openai-model').value.trim();
|
||||||
|
|
||||||
|
let hasError = false;
|
||||||
|
|
||||||
|
if (!apiKey) {
|
||||||
|
document.getElementById('openai-api-key').classList.add('error');
|
||||||
|
hasError = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!baseUrl) {
|
||||||
|
document.getElementById('openai-base-url').classList.add('error');
|
||||||
|
hasError = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!model) {
|
||||||
|
document.getElementById('openai-model').classList.add('error');
|
||||||
|
hasError = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasError) {
|
||||||
|
alert('请填写所有必填字段(标记为 * 的字段)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 收集配置
|
||||||
|
const config = {
|
||||||
|
openai: {
|
||||||
|
api_key: apiKey,
|
||||||
|
base_url: baseUrl,
|
||||||
|
model: model
|
||||||
|
},
|
||||||
|
agent: {
|
||||||
|
max_iterations: parseInt(document.getElementById('agent-max-iterations').value) || 30
|
||||||
|
},
|
||||||
|
tools: []
|
||||||
|
};
|
||||||
|
|
||||||
|
// 收集工具启用状态
|
||||||
|
document.querySelectorAll('#tools-list .tool-item').forEach(item => {
|
||||||
|
const checkbox = item.querySelector('input[type="checkbox"]');
|
||||||
|
const toolName = item.dataset.toolName;
|
||||||
|
if (toolName) {
|
||||||
|
// 直接使用工具名称
|
||||||
|
config.tools.push({
|
||||||
|
name: toolName,
|
||||||
|
enabled: checkbox.checked
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新配置
|
||||||
|
const updateResponse = await fetch('/api/config', {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(config)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!updateResponse.ok) {
|
||||||
|
const error = await updateResponse.json();
|
||||||
|
throw new Error(error.error || '更新配置失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用配置
|
||||||
|
const applyResponse = await fetch('/api/config/apply', {
|
||||||
|
method: 'POST'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!applyResponse.ok) {
|
||||||
|
const error = await applyResponse.json();
|
||||||
|
throw new Error(error.error || '应用配置失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
alert('配置已成功应用!');
|
||||||
|
closeSettings();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('应用配置失败:', error);
|
||||||
|
alert('应用配置失败: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 页面加载时初始化
|
// 页面加载时初始化
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
// 加载对话列表
|
// 加载对话列表
|
||||||
|
|||||||
@@ -18,7 +18,15 @@
|
|||||||
</svg>
|
</svg>
|
||||||
<h1>CyberStrike</h1>
|
<h1>CyberStrike</h1>
|
||||||
</div>
|
</div>
|
||||||
<p class="header-subtitle">安全测试平台</p>
|
<div class="header-right">
|
||||||
|
<p class="header-subtitle">安全测试平台</p>
|
||||||
|
<button class="settings-btn" onclick="openSettings()" title="设置">
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -48,6 +56,64 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 设置模态框 -->
|
||||||
|
<div id="settings-modal" class="modal">
|
||||||
|
<div class="modal-content settings-modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2>系统设置</h2>
|
||||||
|
<span class="modal-close" onclick="closeSettings()">×</span>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body settings-body">
|
||||||
|
<!-- OpenAI配置 -->
|
||||||
|
<div class="settings-section">
|
||||||
|
<h3>OpenAI 配置</h3>
|
||||||
|
<div class="settings-form">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="openai-api-key">API Key <span style="color: red;">*</span></label>
|
||||||
|
<input type="password" id="openai-api-key" placeholder="输入OpenAI API Key" required />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="openai-base-url">Base URL <span style="color: red;">*</span></label>
|
||||||
|
<input type="text" id="openai-base-url" placeholder="https://api.openai.com/v1" required />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="openai-model">模型 <span style="color: red;">*</span></label>
|
||||||
|
<input type="text" id="openai-model" placeholder="gpt-4" required />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- MCP工具配置 -->
|
||||||
|
<div class="settings-section">
|
||||||
|
<h3>MCP 工具配置</h3>
|
||||||
|
<div class="tools-controls">
|
||||||
|
<div class="tools-actions">
|
||||||
|
<button class="btn-secondary" onclick="selectAllTools()">全选</button>
|
||||||
|
<button class="btn-secondary" onclick="deselectAllTools()">全不选</button>
|
||||||
|
<input type="text" id="tools-search" placeholder="搜索工具..." oninput="filterTools()" />
|
||||||
|
</div>
|
||||||
|
<div id="tools-list" class="tools-list"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Agent配置 -->
|
||||||
|
<div class="settings-section">
|
||||||
|
<h3>Agent 配置</h3>
|
||||||
|
<div class="settings-form">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="agent-max-iterations">最大迭代次数</label>
|
||||||
|
<input type="number" id="agent-max-iterations" min="1" max="100" placeholder="30" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn-secondary" onclick="closeSettings()">取消</button>
|
||||||
|
<button class="btn-primary" onclick="applySettings()">应用配置</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- MCP调用详情模态框 -->
|
<!-- MCP调用详情模态框 -->
|
||||||
<div id="mcp-detail-modal" class="modal">
|
<div id="mcp-detail-modal" class="modal">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
|
|||||||
Reference in New Issue
Block a user