Skip to content

Commit 156facf

Browse files
fix(scans): show hostname/IP on scan detail header, not a raw UUID (#613)
The Scan detail page Host field rendered scan.host_id.slice(0,8) — a truncated UUID that's not human-friendly. Backend (api-scans v1.1.0 C-07/AC-08): GET /scans/{id} now resolves the host's hostname + ip_address from the hosts table onto ScanSummary (via host(ip_address) for clean inet->text, COALESCE for NULLs). The list endpoint omits them (the caller already has host context). Frontend (frontend-scan-detail v1.1.0 C-08/AC-08): the Host field renders scan.hostname || scan.ip_address || scan.host_id.slice(0,8) — hostname first, IP fallback, short UUID only as a last resort — still linking to /hosts/$hostId. Verified live: the field now shows 'owas-tst01' instead of '019eccd8'.
1 parent 9a03c37 commit 156facf

10 files changed

Lines changed: 439 additions & 287 deletions

File tree

api/openapi.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5204,6 +5204,14 @@ components:
52045204
properties:
52055205
scan_id: {type: string, format: uuid}
52065206
host_id: {type: string, format: uuid}
5207+
# Human-friendly host label resolved from the hosts table for the
5208+
# scan-detail header (so it shows a hostname/IP, not a raw UUID).
5209+
# Populated on the detail endpoint (GET /scans/{id}); the list
5210+
# endpoint leaves them absent since the caller already has host
5211+
# context. hostname is the registered hostname (may be empty);
5212+
# ip_address is always present for a real host. Spec api-scans.
5213+
hostname: {type: string}
5214+
ip_address: {type: string}
52075215
status: {type: string, description: 'queued | running | completed | failed'}
52085216
trigger_source: {type: string, description: 'on_demand | scheduled'}
52095217
queued_at: {type: string, format: date-time}

frontend/src/api/schema.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3479,6 +3479,8 @@ export interface components {
34793479
scan_id: string;
34803480
/** Format: uuid */
34813481
host_id: string;
3482+
hostname?: string;
3483+
ip_address?: string;
34823484
/** @description queued | running | completed | failed */
34833485
status: string;
34843486
/** @description on_demand | scheduled */

frontend/src/pages/scans/ScanDetailPage.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,9 @@ export function ScanDetailPage() {
143143
>
144144
<Meta label="Host">
145145
<Link to="/hosts/$hostId" params={{ hostId: scan.host_id }} style={{ color: 'var(--ow-link)', textDecoration: 'none' }}>
146-
{scan.host_id.slice(0, 8)}
146+
{/* Human-friendly label: hostname if registered, else IP, else
147+
a short UUID as a last resort (api-scans resolves these). */}
148+
{scan.hostname || scan.ip_address || scan.host_id.slice(0, 8)}
147149
</Link>
148150
</Meta>
149151
<Meta label="Status">{scan.status}</Meta>

frontend/tests/pages/scan-detail.test.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,4 +161,16 @@ describe('frontend-scan-detail', () => {
161161
// No em-dash in copy.
162162
expect(stripComments(PAGE_SRC)).not.toContain('—');
163163
});
164+
165+
// @ac AC-08
166+
test('frontend-scan-detail/AC-08 — Host field shows hostname || ip || short uuid, not a bare uuid', () => {
167+
// The Host Meta uses the hostname-then-IP-then-short-UUID fallback.
168+
expect(PAGE_SRC).toMatch(
169+
/scan\.hostname \|\| scan\.ip_address \|\| scan\.host_id\.slice\(0, 8\)/,
170+
);
171+
// And it is NOT the old bare-UUID render (slice as the sole child).
172+
expect(PAGE_SRC).not.toMatch(/>\s*\{scan\.host_id\.slice\(0, 8\)\}\s*</);
173+
// Still a Link to the host detail page.
174+
expect(PAGE_SRC).toContain('to="/hosts/$hostId"');
175+
});
164176
});

internal/scanresult/reader.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,13 @@ func NewReader(pool *pgxpool.Pool) *Reader { return &Reader{pool: pool} }
3535
// ScanSummary is the scan_runs metadata shown in the list and the scan
3636
// detail header.
3737
type ScanSummary struct {
38-
ScanID uuid.UUID
39-
HostID uuid.UUID
38+
ScanID uuid.UUID
39+
HostID uuid.UUID
40+
// Hostname + IPAddress are the human-friendly host label, resolved
41+
// from the hosts table by GetScan for the detail header. ListByHost
42+
// leaves them empty (the list caller already has host context).
43+
Hostname string
44+
IPAddress string
4045
Status string
4146
TriggerSource string
4247
QueuedAt time.Time
@@ -155,7 +160,11 @@ func (rd *Reader) ListByHost(ctx context.Context, hostID uuid.UUID, limit int, c
155160
return out, next, nil
156161
}
157162

158-
// GetScan returns a scan's metadata, or ErrScanNotFound.
163+
// GetScan returns a scan's metadata, or ErrScanNotFound. It also resolves
164+
// the host's hostname + ip_address from the hosts table so the detail
165+
// header can show a human-friendly label instead of a raw UUID. A missing
166+
// host row (the FK is ON DELETE RESTRICT, so this is unexpected) leaves the
167+
// label fields empty rather than failing the scan read.
159168
func (rd *Reader) GetScan(ctx context.Context, scanID uuid.UUID) (ScanSummary, error) {
160169
run, err := scanruns.Get(ctx, rd.pool, scanID)
161170
if errors.Is(err, scanruns.ErrNotFound) {
@@ -164,7 +173,19 @@ func (rd *Reader) GetScan(ctx context.Context, scanID uuid.UUID) (ScanSummary, e
164173
if err != nil {
165174
return ScanSummary{}, err
166175
}
167-
return summaryFromRun(run), nil
176+
s := summaryFromRun(run)
177+
var hostname, ip string
178+
// host(ip_address) renders the inet as plain text (no /netmask), matching
179+
// how internal/host formats it; COALESCE keeps a NULL hostname/IP as "".
180+
err = rd.pool.QueryRow(ctx,
181+
`SELECT COALESCE(hostname, ''), COALESCE(host(ip_address), '') FROM hosts WHERE id = $1`,
182+
s.HostID).Scan(&hostname, &ip)
183+
if err != nil && !errors.Is(err, pgx.ErrNoRows) {
184+
return ScanSummary{}, fmt.Errorf("scanresult: resolve host label: %w", err)
185+
}
186+
s.Hostname = hostname
187+
s.IPAddress = ip
188+
return s, nil
168189
}
169190

170191
// ScanResults returns every rule's verdict for a scan, ordered by

0 commit comments

Comments
 (0)