fix(ui): 任务总览的测试范围显示企业名称而非 ID

/api/tasks/{id}/scope 只返回 company_id,前端无从解析名称,测试范围里的
企业行只能渲染成 "company #4"。

- TaskScope 增加 company_name,ListTaskScope 与 ListTaskScopeWithSources
  都 LEFT JOIN companies 带出名称(LEFT 而非 INNER:域名/IP/网段行的
  company_id 为 NULL,内连接会把它们全过滤掉)
- 两个查询函数都改,避免结构体字段"一个填一个不填"的不一致
- 前端优先显示名称,公司被删后的残留行仍以「企业 #N」兜底

覆盖图谱的企业节点本就查名显示(task_scope.go 的 BuildCoverageGraph),
不受影响。

Claude-Session: https://claude.ai/code/session_01Q8nhYZfS4H8fsSCBJXxbSy
This commit is contained in:
Autumn-27 committed 2026-08-22 06:43:08 -04:00
1 parent f0c2b43802
commit 76c648d36d
4 files changed
+18 -10

No files matched your search

+3 -2
View File
@@ -52,10 +52,11 @@ tested AS (
// scopes of its direct source tasks. TaskScope.TaskID preserves provenance.
func (s *AssetStore) ListTaskScopeWithSources(taskID int64) ([]TaskScope, error) {
rows, err := s.db.Query(`WITH `+directTaskContextCTE+`
SELECT ts.id, ts.task_id, ts.kind, COALESCE(ts.company_id,0), COALESCE(ts.domain,''),
SELECT ts.id, ts.task_id, ts.kind, COALESCE(ts.company_id,0), COALESCE(c.name,''), COALESCE(ts.domain,''),
COALESCE(ts.net::text,''), ts.source, COALESCE(ts.reason,'')
FROM task_scope ts
JOIN context_tasks ctx ON ctx.task_id=ts.task_id
LEFT JOIN companies c ON c.id=ts.company_id
ORDER BY CASE WHEN ts.task_id=$1 THEN 0 ELSE 1 END, ts.id`, taskID)
if err != nil {
return nil, err
@@ -65,7 +66,7 @@ ORDER BY CASE WHEN ts.task_id=$1 THEN 0 ELSE 1 END, ts.id`, taskID)
for rows.Next() {
var scope TaskScope
var companyID int64
if err := rows.Scan(&scope.ID, &scope.TaskID, &scope.Kind, &companyID, &scope.Domain, &scope.Net, &scope.Source, &scope.Reason); err != nil {
if err := rows.Scan(&scope.ID, &scope.TaskID, &scope.Kind, &companyID, &scope.CompanyName, &scope.Domain, &scope.Net, &scope.Source, &scope.Reason); err != nil {
return nil, err
}
if companyID > 0 {
+13 -7
View File
@@ -16,10 +16,13 @@ type TaskScope struct {
TaskID int64 `json:"task_id"`
Kind string `json:"kind"` // company|root_domain|subdomain|ip|cidr
CompanyID *int64 `json:"company_id,omitempty"`
Domain string `json:"domain,omitempty"`
Net string `json:"net,omitempty"`
Source string `json:"source"`
Reason string `json:"reason,omitempty"`
// CompanyName is resolved for kind=company so callers can label a scope row
// without a second lookup. Empty when the row is not a company reference.
CompanyName string `json:"company_name,omitempty"`
Domain string `json:"domain,omitempty"`
Net string `json:"net,omitempty"`
Source string `json:"source"`
Reason string `json:"reason,omitempty"`
}
// stripHostPort drops a trailing :port from a host:port / ip:port / [ipv6]:port
@@ -202,8 +205,11 @@ func (s *AssetStore) DeleteTaskScope(taskID, scopeID int64) (bool, error) {
// ListTaskScope returns all scope rows for a task.
func (s *AssetStore) ListTaskScope(taskID int64) ([]TaskScope, error) {
rows, err := s.db.Query(`
SELECT id, kind, COALESCE(company_id,0), COALESCE(domain,''), COALESCE(net::text,''), source, COALESCE(reason,'')
FROM task_scope WHERE task_id=$1 ORDER BY id`, taskID)
SELECT ts.id, ts.kind, COALESCE(ts.company_id,0), COALESCE(c.name,''), COALESCE(ts.domain,''),
COALESCE(ts.net::text,''), ts.source, COALESCE(ts.reason,'')
FROM task_scope ts
LEFT JOIN companies c ON c.id=ts.company_id
WHERE ts.task_id=$1 ORDER BY ts.id`, taskID)
if err != nil {
return nil, err
}
@@ -212,7 +218,7 @@ FROM task_scope WHERE task_id=$1 ORDER BY id`, taskID)
for rows.Next() {
var t TaskScope
var cid int64
if err := rows.Scan(&t.ID, &t.Kind, &cid, &t.Domain, &t.Net, &t.Source, &t.Reason); err != nil {
if err := rows.Scan(&t.ID, &t.Kind, &cid, &t.CompanyName, &t.Domain, &t.Net, &t.Source, &t.Reason); err != nil {
return nil, err
}
t.TaskID = taskID
@@ -41,7 +41,7 @@ function cacheHitRate(cacheRead: number, input: number): string {
function scopeValue(row: TaskScopeRow): string {
if (row.domain) return row.domain;
if (row.net) return row.net;
if (row.company_id) return `company #${row.company_id}`;
if (row.company_id) return row.company_name || `企业 #${row.company_id}`;
return "—";
}
+1
View File
@@ -241,6 +241,7 @@ export interface TaskScopeRow {
task_id: number;
kind: "company" | "root_domain" | "subdomain" | "ip" | "cidr";
company_id?: number;
company_name?: string; // 后端 JOIN companies 解析,仅 kind=company 有值
domain?: string;
net?: string;
source: "auto" | "agent" | "manual";