feat(hosts): per-host tags edit + dashboard chip-row filter (P4-07)

This commit is contained in:
2026-05-05 11:16:09 +01:00
parent c1426110e5
commit 168059ae45
8 changed files with 183 additions and 7 deletions
+43
View File
@@ -299,6 +299,49 @@ func (s *Store) SetHostBandwidth(ctx context.Context, hostID string, upKBps, dow
return nil
}
// SetHostTags replaces the host's tag list. Tags are passed already
// normalised (lowercase, deduped) by the caller — store-layer just
// JSON-marshals and writes. Empty slice clears all tags.
func (s *Store) SetHostTags(ctx context.Context, hostID string, tags []string) error {
if tags == nil {
tags = []string{}
}
b, err := json.Marshal(tags)
if err != nil {
return fmt.Errorf("store: marshal tags: %w", err)
}
_, err = s.db.ExecContext(ctx,
`UPDATE hosts SET tags = ? WHERE id = ?`, string(b), hostID)
if err != nil {
return fmt.Errorf("store: set host tags: %w", err)
}
return nil
}
// DistinctHostTags returns the union of every tag in use across the
// fleet, sorted. Powers the autocomplete on the host-tags editor and
// the chip-row filter on the dashboard. Cheap at fleet sizes this
// codebase targets — re-query on each render is fine.
func (s *Store) DistinctHostTags(ctx context.Context) ([]string, error) {
rows, err := s.db.QueryContext(ctx,
`SELECT DISTINCT json_each.value
FROM hosts, json_each(hosts.tags)
ORDER BY 1`)
if err != nil {
return nil, fmt.Errorf("store: distinct host tags: %w", err)
}
defer func() { _ = rows.Close() }()
var out []string
for rows.Next() {
var t string
if err := rows.Scan(&t); err != nil {
return nil, err
}
out = append(out, t)
}
return out, rows.Err()
}
func nullableInt(p *int) any {
if p == nil {
return nil