feat(traffic): 流量列表新增「清空全部」,并借空库时机压实索引
一次删除全部流量记录,忽略当前筛选条件。与按 host 删除的两条路径相比多做 两件事: - 清理索引已不再记录的历史 host 目录。按 host 删除只能从索引反查目录, 行已消失的孤立目录会一直留着;「清空全部」既然语义是不留残余,就直接 扫顶层目录(跳过 _index/_blobs/_ca/_delete_staging)。 - 收尾做一次全量压实(optimize + VACUUM + wal_checkpoint(TRUNCATE)),把 索引占用的磁盘空间还给系统,并把实际释放量返回给前端提示。 VACUUM 的耗时与临时空间只和它保留的内容成正比,所以空库是做全量压实唯一 免费的时机——也正因如此,这一步顺带成为已有实例的转换途径:auto_vacuum 只能通过 VACUUM 生效,清空一次之后旧库即转为增量回收模式,此后日常按 host 删除就能自行回收空间(TestDeleteAllConvertsLegacyIndex 覆盖)。 已绑定到漏洞的流量证据保存在独立的证据库中,不受影响——绑定时已拷出独立 副本,正是为了让热流量可以随时清空而不带走证据。确认弹窗里明确写了这点。 前端把「清空全部」做成 outline 样式而非第二个 destructive 按钮:它忽略一切 筛选条件,不该看起来和「删除该目标」只差一次误点;无流量时禁用。 测试:新增 TestDeleteAllPurgesAndCompacts(清空后索引、正文、blob 引用与 孤立目录全清,空间回收到 1/8 以下,清空后仍可继续录制)与 TestDeleteAllConvertsLegacyIndex(旧库经清空后转为增量回收,随后普通删除 即可回收)。
This commit is contained in:
1 parent
61d486160e
commit
2f5f7c0227
6 files changed
+314
-24
No files matched your search
@@ -784,6 +784,7 @@ func (s *Server) Handler() http.Handler {
|
||||
mux.HandleFunc("GET /api/traffic/hosts", s.getTrafficHosts)
|
||||
mux.HandleFunc("DELETE /api/traffic", s.deleteTraffic)
|
||||
mux.HandleFunc("DELETE /api/traffic/hosts", s.deleteTrafficHosts)
|
||||
mux.HandleFunc("DELETE /api/traffic/all", s.deleteAllTraffic)
|
||||
mux.HandleFunc("GET /api/traffic/exchange", s.getTrafficExchange)
|
||||
mux.HandleFunc("GET /api/traffic/blob", s.getTrafficBlob)
|
||||
mux.HandleFunc("GET /api/commands", s.pgListCommands)
|
||||
@@ -3185,6 +3186,25 @@ func (s *Server) deleteTrafficHosts(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, 200, map[string]any{"deleted": n})
|
||||
}
|
||||
|
||||
// deleteAllTraffic purges every recorded exchange, then compacts the index so
|
||||
// the space is actually returned to the filesystem — an emptied index is the one
|
||||
// moment a full rewrite is cheap. Evidence already bound to a finding lives in
|
||||
// the evidence store and is deliberately left alone. Returns the number of
|
||||
// exchanges deleted and the bytes of index reclaimed.
|
||||
func (s *Server) deleteAllTraffic(w http.ResponseWriter, r *http.Request) {
|
||||
tr := s.m.Traffic()
|
||||
if tr == nil {
|
||||
writeErr(w, 404, "traffic disabled")
|
||||
return
|
||||
}
|
||||
n, reclaimed, err := tr.DeleteAll()
|
||||
if err != nil {
|
||||
writeErr(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, 200, map[string]any{"deleted": n, "reclaimed": reclaimed})
|
||||
}
|
||||
|
||||
// getTrafficExchange returns the full raw request/response of one exchange,
|
||||
// read on demand from the traffic tree (bodies are not in the paged list).
|
||||
func (s *Server) getTrafficExchange(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Reference in new issue
Block a user