Files
ARTEX-chixuweihu/server/task_asset_mapping.go
T

83 lines
3.2 KiB
Go

package server
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"github.com/Autumn-27/artex/db"
)
// taskAssetMapping starts a visible asset-mapper conversation for the task and
// then lets the graph refresh independently. The graph itself remains sourced
// from the database; the conversation is the audit trail for the explanation.
func (s *Server) taskAssetMapping(w http.ResponseWriter, r *http.Request) {
t, ok := s.m.Task(r.PathValue("id"))
if !ok {
writeErr(w, http.StatusNotFound, "task not found")
return
}
if s.m.pg == nil || s.chatAgentRef() == nil {
writeErr(w, http.StatusServiceUnavailable, s.chatUnavailableReason())
return
}
if agentInfo, err := s.m.pg.GetAgentByKey("asset_mapper"); err != nil || agentInfo == nil || !agentInfo.Enabled {
writeErr(w, http.StatusServiceUnavailable, "资产梳理 Agent 未启用")
return
}
taskID, _ := strconv.ParseInt(t.ID, 10, 64)
message := fmt.Sprintf("请重新梳理任务 #%d 的资产关系。只使用当前任务及其直接关联任务的真实资产数据,按互联网入口、边界服务、内网业务三层说明关系;不要探测、不要修改资产库,也不要把没有证据的连接描述成已验证通路。", taskID)
if as := s.m.Assets(); as != nil {
if graph, err := as.BuildCoverageGraph(taskID, t.ExpID); err == nil {
type nodeSample struct {
Key, Kind, Label, Domain, IP string
Port int
Tested, InScope bool
}
counts := map[string]int{}
sample := make([]nodeSample, 0, 60)
for _, node := range graph.Nodes {
counts[node.Kind]++
if len(sample) < 60 {
sample = append(sample, nodeSample{
Key: node.Key, Kind: node.Kind, Label: node.Label, Domain: node.Domain,
IP: node.IP, Port: node.Port, Tested: node.Tested, InScope: node.InScope,
})
}
}
edges := graph.Edges
truncatedEdges := len(edges) > 80
if truncatedEdges {
edges = edges[:80]
}
snapshot, _ := json.Marshal(map[string]any{
"nodes_total": len(graph.Nodes), "edges_total": len(graph.Edges),
"counts_by_kind": counts, "node_sample": sample, "edge_sample": edges,
"truncated": len(graph.Nodes) > len(sample) || truncatedEdges,
})
message += "\n\n页面刚读取的任务资产图真实摘要如下;只依据摘要和任务范围解释,避免把全局资产误当成本任务资产。未列出的节点不要逐一补写:\n" + string(snapshot)
}
}
title := fmt.Sprintf("资产梳理 · 任务 #%d", taskID)
conversation, err := s.m.pg.CreateConversation("asset_mapper", title, nil)
if err != nil || conversation == nil {
writeErr(w, http.StatusInternalServerError, "创建资产梳理会话失败")
return
}
if _, err := s.m.pg.AppendConvActivity(conversation.ID, db.Activity{
Worker: "asset_mapper", Kind: "user", Summary: title, Detail: message,
}); err != nil {
writeErr(w, http.StatusInternalServerError, "写入资产梳理会话失败")
return
}
busyKey := s.convBusyKey(conversation.ID)
s.chatMu.Lock()
s.chatBusy[busyKey] = true
s.chatMu.Unlock()
s.runConversation(conversation, message, busyKey, message)
writeJSON(w, http.StatusAccepted, map[string]any{
"status": "accepted", "agent_key": "asset_mapper", "conversation_id": conversation.ID,
})
}