feat: 新增发现详情页(多 Tab + 状态区可改严重度/处置状态)
详情页 /function/findings/detail?id=<finding_id>:
- 概览 Tab:左栏摘要 + 证据/PoC,右栏状态区(严重等级、漏洞类型、涉及资产、处理状态、所属任务、发现时间)
- 严重等级与处理状态用下拉,选择后直接调后端接口即时修正
- 另有「证据/PoC」「元数据」两个 Tab
- 发现列表页漏洞名称改为链接进入详情
后端:
- GET /api/exploration/findings/{id} 取单条(含资产解析)
- PATCH /api/exploration/findings/{id} 扩展为部分更新 {status?, severity?}
- db.GetFinding / db.SetFindingSeverity(同步探索节点 payload,保持任务内视图一致)
- db.ValidSeverity 白名单;api 增加 getFinding / setFindingSeverity
- mock 支持单条 GET / PATCH,并回填 finding_id 供 demo 链接
db 测试补充 GetFinding / SetFindingSeverity 往返断言;next build 通过。
This commit is contained in:
1 parent
c69534e09b
commit
64d9c66f6a
7 files changed
+552
-18
No files matched your search
+65
-10
@@ -607,7 +607,8 @@ func (s *Server) Handler() http.Handler {
|
||||
mux.HandleFunc("GET /api/exploration/frontier", s.frontier)
|
||||
mux.HandleFunc("GET /api/exploration/findings", s.findings)
|
||||
mux.HandleFunc("GET /api/exploration/findings/stats", s.findingStats)
|
||||
mux.HandleFunc("PATCH /api/exploration/findings/{id}", s.setFindingStatus)
|
||||
mux.HandleFunc("GET /api/exploration/findings/{id}", s.getFinding)
|
||||
mux.HandleFunc("PATCH /api/exploration/findings/{id}", s.patchFinding)
|
||||
mux.HandleFunc("GET /api/exploration/intents", s.intents)
|
||||
mux.HandleFunc("GET /api/exploration/graph", s.explorationGraph)
|
||||
mux.HandleFunc("GET /api/exploration/activity", s.activity)
|
||||
@@ -1464,35 +1465,89 @@ func (s *Server) findingStats(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, 200, st)
|
||||
}
|
||||
|
||||
// setFindingStatus updates one finding's triage state (pending / false_positive /
|
||||
// ignored / resolved). The id is the standalone findings-table id (DTO finding_id).
|
||||
func (s *Server) setFindingStatus(w http.ResponseWriter, r *http.Request) {
|
||||
// getFinding returns one finding by its standalone-table id (DTO finding_id),
|
||||
// with anchored assets resolved for display.
|
||||
func (s *Server) getFinding(w http.ResponseWriter, r *http.Request) {
|
||||
id := int64(atoiDefault(r.PathValue("id"), 0))
|
||||
if id <= 0 {
|
||||
writeErr(w, 400, "bad finding id")
|
||||
return
|
||||
}
|
||||
f, err := s.m.pg.GetFinding(id)
|
||||
if err != nil {
|
||||
writeErr(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
if f == nil {
|
||||
writeErr(w, 404, "finding not found")
|
||||
return
|
||||
}
|
||||
assets := s.resolveAssetIDs(f.AssetIDs)
|
||||
writeJSON(w, 200, findingFromDB(f, assets))
|
||||
}
|
||||
|
||||
// patchFinding partially updates a finding: any subset of {status, severity}.
|
||||
// The id is the standalone findings-table id (DTO finding_id). Severity edits are
|
||||
// mirrored onto the originating exploration node so the per-task view stays in
|
||||
// sync. Returns the updated finding DTO.
|
||||
func (s *Server) patchFinding(w http.ResponseWriter, r *http.Request) {
|
||||
id := int64(atoiDefault(r.PathValue("id"), 0))
|
||||
if id <= 0 {
|
||||
writeErr(w, 400, "bad finding id")
|
||||
return
|
||||
}
|
||||
var body struct {
|
||||
Status string `json:"status"`
|
||||
Status *string `json:"status"`
|
||||
Severity *string `json:"severity"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
writeErr(w, 400, "bad json: "+err.Error())
|
||||
return
|
||||
}
|
||||
if !db.ValidFindingStatus(body.Status) {
|
||||
writeErr(w, 400, "bad status: "+body.Status)
|
||||
if body.Status == nil && body.Severity == nil {
|
||||
writeErr(w, 400, "nothing to update: provide status and/or severity")
|
||||
return
|
||||
}
|
||||
n, err := s.m.pg.SetFindingStatus(id, body.Status)
|
||||
if body.Status != nil {
|
||||
if !db.ValidFindingStatus(*body.Status) {
|
||||
writeErr(w, 400, "bad status: "+*body.Status)
|
||||
return
|
||||
}
|
||||
n, err := s.m.pg.SetFindingStatus(id, *body.Status)
|
||||
if err != nil {
|
||||
writeErr(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
if n == 0 {
|
||||
writeErr(w, 404, "finding not found")
|
||||
return
|
||||
}
|
||||
}
|
||||
if body.Severity != nil {
|
||||
if !db.ValidSeverity(*body.Severity) {
|
||||
writeErr(w, 400, "bad severity: "+*body.Severity)
|
||||
return
|
||||
}
|
||||
n, err := s.m.pg.SetFindingSeverity(id, *body.Severity)
|
||||
if err != nil {
|
||||
writeErr(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
if n == 0 {
|
||||
writeErr(w, 404, "finding not found")
|
||||
return
|
||||
}
|
||||
}
|
||||
f, err := s.m.pg.GetFinding(id)
|
||||
if err != nil {
|
||||
writeErr(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
if n == 0 {
|
||||
if f == nil {
|
||||
writeErr(w, 404, "finding not found")
|
||||
return
|
||||
}
|
||||
writeJSON(w, 200, map[string]any{"id": i64s(id), "status": body.Status})
|
||||
writeJSON(w, 200, findingFromDB(f, s.resolveAssetIDs(f.AssetIDs)))
|
||||
}
|
||||
|
||||
func (s *Server) intents(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Reference in new issue
Block a user