feat: add device custom fields#1
Merged
danielgatis merged 8 commits intomasterfrom Apr 28, 2026
Merged
Conversation
Add `CustomFields map[string]string` to the Device model (BSON/JSON tagged) and a `*map[string]string` pointer field to DeviceUpdate so that omitting the field in a PUT payload leaves existing values untouched.
- Service: apply CustomFields from request when non-nil; register
"custom_fields" as a valid filter field with "contains" operator
- MongoDB: route custom_fields filter via ParseCustomFieldsFilter using
\$objectToArray + \$regexMatch to search across all map values
- PostgreSQL entity: add JSONB column mapping to Device entity
- PostgreSQL filters: add fromCustomFieldsFilter that generates an
EXISTS (jsonb_each_text) subquery for ILIKE value matching
- Migration 002: add custom_fields jsonb column with default '{}'
golangci-lint uses -fuse-ld=gold which requires binutils-gold on Alpine ARM64. Without it the build fails with "cannot find 'ld'".
- Device list: new Custom Fields column showing key/value badges with pill styling; search also queries custom field values - Device detail: CustomFieldsSection component with add form, inline delete confirmation, and useUpdateDeviceCustomFields mutation - useDevices: extend buildFilter to OR-search across custom_fields values - useDeviceMutations: export useUpdateDeviceCustomFields hook
- pg/internal: TestFromCustomFieldsFilter and TestParseFilterProperty_CustomFields - mongo/internal: TestParseCustomFieldsFilter covering contains, unsupported operators, and non-string value error - services: extend TestDeviceUpdate with cases for setting, clearing, and nil (no-op) custom fields
…fields - Devices list: rendering, loading, empty state, row navigation, custom fields column (empty, single field, multiple fields), error state - DeviceDetails: loading spinner, device data rendering, custom fields section display, add form, delete confirmation flow, mutation calls, duplicate key validation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
As device fleets grow, operators often need to attach context-specific metadata to individual devices — things like physical location, environment tags, asset identifiers, or team ownership. Without a dedicated mechanism, this information ends up scattered across external spreadsheets or encoded into hostnames, making it hard to query and maintain.
This feature introduces custom fields: a simple key-value map attached to each device. Operators can freely define any metadata they need, and that metadata becomes a first-class citizen in the dashboard — visible in the device list, searchable alongside the hostname, and fully manageable from the device detail page without touching the API directly.
Screen.Recording.2026-04-28.at.8.17.06.AM.mov
Summary
custom_fields map[string]stringto the Device model, stored as JSONB in PostgreSQL and an embedded document in MongoDBPUT /devices/{uid}endpoint — nil means no change, empty map clears all fieldsChanges
pkgCustomFieldstoDevicemodel andDeviceUpdaterequest DTOapi$objectToArrayfilter, PostgreSQL JSONB filter, migration 002openapicustom_fieldsto device schema and PUT request bodydockerbinutils-goldto Alpine images for ARM64 linker support (see note below)ui(Vue)ui-reacttest(api)test(ui-react)Note: binutils-gold in Alpine Dockerfiles
During local development on an ARM64 machine (Apple Silicon), the Docker builds were failing with:
The root cause is that
golangci-lintis compiled with-fuse-ld=gold, which requires thegoldlinker (binutils-goldpackage) to be present on the system. Alpine's defaultbinutilsdoes not include it. Addingbinutils-goldto the Alpine images that installgolangci-lintresolves the build failure on ARM64 hosts.Test plan
go test ./api/services/... ./api/store/pg/internal/... ./api/store/mongo/internal/...cd ui-react/apps/console && npx vitest run src/pages/devices/__tests__PUT /devices/{uid}with{"name": "x", "custom_fields": {"env": "prod"}}→ 200GET /devices/{uid}→ response includescustom_fields/devicesby custom field value returns matching devices