From 0a65742ca1621651874d24a67ae0ce7c94a0ed42 Mon Sep 17 00:00:00 2001 From: Javier Marcos <1271349+javuto@users.noreply.github.com> Date: Tue, 11 Aug 2026 17:10:27 +0200 Subject: [PATCH 1/2] Fix for tagging nodes throwing a 500 error --- frontend/src/features/environments/EnvConfigPage.test.tsx | 2 +- .../src/features/environments/EnvironmentsPage.test.tsx | 2 +- frontend/src/features/nodes/NodeDetailPage.test.tsx | 6 +++--- frontend/src/features/nodes/NodesTablePage.test.tsx | 4 ++-- frontend/src/features/tags/TagsPage.test.tsx | 2 +- frontend/src/features/tags/TagsPage.tsx | 4 ++-- pkg/environments/environments.go | 2 +- pkg/tags/tags.go | 7 +++++-- 8 files changed, 16 insertions(+), 13 deletions(-) diff --git a/frontend/src/features/environments/EnvConfigPage.test.tsx b/frontend/src/features/environments/EnvConfigPage.test.tsx index bd5dd3d5..8ba2274c 100644 --- a/frontend/src/features/environments/EnvConfigPage.test.tsx +++ b/frontend/src/features/environments/EnvConfigPage.test.tsx @@ -92,7 +92,7 @@ function makeEnv(overrides: Partial = {}): TLSEnvironment { msi_package: '', pkg_package: '', debug_http: false, - icon: 'fas fa-wrench', + icon: 'wrench', options: '{}', schedule: '{}', packs: '{}', diff --git a/frontend/src/features/environments/EnvironmentsPage.test.tsx b/frontend/src/features/environments/EnvironmentsPage.test.tsx index 1723b4f1..53b96f10 100644 --- a/frontend/src/features/environments/EnvironmentsPage.test.tsx +++ b/frontend/src/features/environments/EnvironmentsPage.test.tsx @@ -66,7 +66,7 @@ function makeEnv(overrides: Partial = {}): TLSEnvironment { msi_package: '', pkg_package: '', debug_http: false, - icon: 'fas fa-wrench', + icon: 'wrench', options: '{}', schedule: '{}', packs: '{}', diff --git a/frontend/src/features/nodes/NodeDetailPage.test.tsx b/frontend/src/features/nodes/NodeDetailPage.test.tsx index 027b10c0..a21ec25a 100644 --- a/frontend/src/features/nodes/NodeDetailPage.test.tsx +++ b/frontend/src/features/nodes/NodeDetailPage.test.tsx @@ -188,7 +188,7 @@ function makeTags(): AdminTag[] { name: 'prod', description: 'Production', color: '#2ecc71', - icon: 'fas fa-tag', + icon: 'tag', created_by: 'alice', custom_tag: 'tag', auto_tag: false, @@ -553,7 +553,7 @@ describe('NodeDetailPage', () => { name: 'prod', description: 'Production', color: '#2ecc71', - icon: 'fas fa-tag', + icon: 'tag', created_by: 'alice', custom_tag: 'tag', auto_tag: false, @@ -568,7 +568,7 @@ describe('NodeDetailPage', () => { name: 'critical', description: 'Critical', color: '#e74c3c', - icon: 'fas fa-tag', + icon: 'tag', created_by: 'alice', custom_tag: 'tag', auto_tag: false, diff --git a/frontend/src/features/nodes/NodesTablePage.test.tsx b/frontend/src/features/nodes/NodesTablePage.test.tsx index f6304ffa..d0194ef3 100644 --- a/frontend/src/features/nodes/NodesTablePage.test.tsx +++ b/frontend/src/features/nodes/NodesTablePage.test.tsx @@ -453,7 +453,7 @@ describe('NodesTablePage', () => { name: 'prod', description: 'Production', color: '#2ecc71', - icon: 'fas fa-tag', + icon: 'tag', created_by: 'alice', custom_tag: 'tag', auto_tag: false, @@ -468,7 +468,7 @@ describe('NodesTablePage', () => { name: 'critical', description: 'Critical', color: '#e74c3c', - icon: 'fas fa-tag', + icon: 'tag', created_by: 'alice', custom_tag: 'tag', auto_tag: false, diff --git a/frontend/src/features/tags/TagsPage.test.tsx b/frontend/src/features/tags/TagsPage.test.tsx index 462b85a7..d6fc0d19 100644 --- a/frontend/src/features/tags/TagsPage.test.tsx +++ b/frontend/src/features/tags/TagsPage.test.tsx @@ -49,7 +49,7 @@ function makeTag(overrides: Partial = {}): AdminTag { name: 'production', description: 'Production environment', color: '#5b8def', - icon: 'fas fa-server', + icon: 'server', created_by: 'admin', custom_tag: 'tag', auto_tag: false, diff --git a/frontend/src/features/tags/TagsPage.tsx b/frontend/src/features/tags/TagsPage.tsx index 87d9c957..d1a2a8b2 100644 --- a/frontend/src/features/tags/TagsPage.tsx +++ b/frontend/src/features/tags/TagsPage.tsx @@ -18,7 +18,7 @@ type ModalMode = | { kind: 'delete'; tag: AdminTag }; const DEFAULT_COLOR = '#5b8def'; -const DEFAULT_ICON = 'fas fa-tag'; +const DEFAULT_ICON = 'tag'; const TAG_TYPE_REGULAR = 6; // mirrors pkg/tags.TagTypeTag export function TagsPage() { @@ -550,7 +550,7 @@ function TagFormModal({ )} />

- Font Awesome class (e.g. fas fa-server). + Icon name (e.g. server, tag, wrench).

diff --git a/pkg/environments/environments.go b/pkg/environments/environments.go index 66360e33..0674999e 100644 --- a/pkg/environments/environments.go +++ b/pkg/environments/environments.go @@ -32,7 +32,7 @@ const ( // DefaultCarverBlockPath as default block endpoint for the carver DefaultCarverBlockPath string = "block" // DefaultEnvironmentIcon as default icon to use for environments - DefaultEnvironmentIcon string = "fas fa-wrench" + DefaultEnvironmentIcon string = "wrench" // DefaultEnvironmentType as default type to use for environments DefaultEnvironmentType string = "osquery" // DefaultSecretLength as default length for secrets diff --git a/pkg/tags/tags.go b/pkg/tags/tags.go index b15a9e90..d6cbf62a 100644 --- a/pkg/tags/tags.go +++ b/pkg/tags/tags.go @@ -13,7 +13,7 @@ import ( const ( // DefaultTagIcon as default icon to use for tags - DefaultTagIcon string = "fas fa-tag" + DefaultTagIcon string = "tag" // DefaultAutoTagUser as default user ID to be used for auto tagging DefaultAutoTagUser uint = 0 // DefaultAutocreated as default username and description for tags @@ -435,7 +435,10 @@ func (m *TagManager) TagNode(name string, node nodes.OsqueryNode, user string, a tag = newTag } if m.IsTagged(tag.Name, node) { - return fmt.Errorf("node already tagged") + // Idempotent: tagging an already-tagged node is not an error. + // This makes batch-tagging safe — the frontend can tag all selected + // nodes without checking which are already tagged. + return nil } tagged := TaggedNode{ Tag: tag.Name, From c30001a0960c5f437d97213f228fe776c674de90 Mon Sep 17 00:00:00 2001 From: Javier Marcos <1271349+javuto@users.noreply.github.com> Date: Tue, 11 Aug 2026 17:25:28 +0200 Subject: [PATCH 2/2] Display tag icon --- frontend/src/components/forms/IconPicker.tsx | 9 ++++-- frontend/src/features/nodes/nodeSignals.tsx | 33 +++++++++++--------- frontend/src/features/tags/TagsPage.tsx | 22 +++++-------- 3 files changed, 33 insertions(+), 31 deletions(-) diff --git a/frontend/src/components/forms/IconPicker.tsx b/frontend/src/components/forms/IconPicker.tsx index f1cc934e..4a365f23 100644 --- a/frontend/src/components/forms/IconPicker.tsx +++ b/frontend/src/components/forms/IconPicker.tsx @@ -9,14 +9,15 @@ import { useState, useMemo, useRef, useEffect } from 'react'; import { Server, Cloud, Shield, Lock, Globe, Network, Monitor, Laptop, HardDrive, Database, Terminal, Bug, Cog, Wrench, Cpu, Radar, - Router, Flame, Bolt, Rocket, Satellite, Building, + Router, Flame, Bolt, Rocket, Satellite, Building, Warehouse, Factory, Store, School, Hospital, Fingerprint, Key, Eye, Search, ChartLine, ChartBar, Bell, Flag, Star, - Heart, Leaf, Anchor, Plane, Drone, type LucideIcon, + Heart, Leaf, Anchor, Plane, Drone, Tag, type LucideIcon, } from 'lucide-react'; import { cn } from '$/lib/cn'; const PRESET_ICONS: { name: string; Icon: LucideIcon }[] = [ + { name: 'tag', Icon: Tag }, { name: 'server', Icon: Server }, { name: 'cloud', Icon: Cloud }, { name: 'shield', Icon: Shield }, @@ -34,7 +35,6 @@ const PRESET_ICONS: { name: string; Icon: LucideIcon }[] = [ { name: 'cpu', Icon: Cpu }, { name: 'radar', Icon: Radar }, { name: 'router', Icon: Router }, - { name: 'network', Icon: Network }, // deduped below — remove { name: 'flame', Icon: Flame }, { name: 'bolt', Icon: Bolt }, { name: 'rocket', Icon: Rocket }, @@ -74,6 +74,9 @@ export function resolveEnvIcon(icon: string | undefined | null): LucideIcon | nu return ICON_MAP[name] ?? null; } +/** Alias for resolveEnvIcon — tags use the same icon set. */ +export const resolveTagIcon = resolveEnvIcon; + interface IconPickerProps { value: string; onChange: (icon: string) => void; diff --git a/frontend/src/features/nodes/nodeSignals.tsx b/frontend/src/features/nodes/nodeSignals.tsx index 7c2b5f5b..393d9bd7 100644 --- a/frontend/src/features/nodes/nodeSignals.tsx +++ b/frontend/src/features/nodes/nodeSignals.tsx @@ -1,5 +1,6 @@ import type { AdminTag, NodeHealth, NodeHealthStatus } from '$/api/types'; import { cn } from '$/lib/cn'; +import { resolveTagIcon } from '$/components/forms/IconPicker'; const HEALTH_LABELS: Record = { healthy: 'healthy', @@ -57,20 +58,24 @@ export function TagChips({ const overflow = list.length - visible.length; return (
- {visible.map((tag) => ( - - {tag.name} - - ))} + {visible.map((tag) => { + const IconComp = resolveTagIcon(tag.icon); + return ( + + {IconComp && } + {tag.name} + + ); + })} {overflow > 0 && ( +{overflow} diff --git a/frontend/src/features/tags/TagsPage.tsx b/frontend/src/features/tags/TagsPage.tsx index d1a2a8b2..74cb2ca4 100644 --- a/frontend/src/features/tags/TagsPage.tsx +++ b/frontend/src/features/tags/TagsPage.tsx @@ -10,6 +10,7 @@ import { cn } from '$/lib/cn'; import { SkeletonRow } from '$/components/data/Skeleton'; import { EmptyState } from '$/components/data/EmptyState'; import { ModalShell } from '$/components/feedback/ModalShell'; +import { IconPicker, resolveTagIcon } from '$/components/forms/IconPicker'; type ModalMode = | { kind: 'closed' } @@ -278,7 +279,10 @@ export function TagsPage() { color: tag.color || DEFAULT_COLOR, }} > - + {(() => { + const IconComp = resolveTagIcon(tag.icon || DEFAULT_ICON); + return IconComp ? : null; + })()} {tag.name} @@ -535,23 +539,13 @@ function TagFormModal({
- setIcon(e.target.value)} - placeholder={DEFAULT_ICON} - className={cn( - 'w-full px-3 py-2 text-sm rounded-md border border-[color:var(--border)]', - 'bg-[color:var(--bg-2)] text-[color:var(--text-1)] font-mono-tabular', - 'focus:outline focus:outline-2 focus:outline-[color:var(--signal)]', - )} + onChange={setIcon} /> -

- Icon name (e.g. server, tag, wrench). -