Enhance NewHost and ObjectForm Components with Additional Device Info Fields
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good

- Updated NewHost component to include new fields for 'deviceInfo.user.shell' and 'deviceInfo.process.execPath', improving the detail of host information captured.
- Enhanced ObjectForm component to handle nested property paths correctly, ensuring computed values do not overwrite parent objects when sibling properties exist.
- Modified ObjectInfo component to correctly display values for object properties, improving data representation.
- Refactored Host model to include new device information fields, enhancing the overall data structure for hosts.
This commit is contained in:
Tom Butcher 2026-09-01 13:04:05 +01:00
parent 2d881cb152
commit 589acab592
4 changed files with 80 additions and 10 deletions

View File

@ -40,14 +40,16 @@ const NewHost = ({ onOk }) => {
_id: false,
createdAt: false,
updatedAt: false,
operatingSystem: false,
'deviceInfo.os.platform': false,
'deviceInfo.os': false,
'deviceInfo.os.hostname': false,
'deviceInfo.cpu': false,
'deviceInfo.cpu.model': false,
'deviceInfo.user.username': false,
'deviceInfo.user.homedir': false,
'deviceInfo.user.shell': false,
'deviceInfo.process.nodeVersion': false,
'deviceInfo.process.execPath': false,
files: false
}}
/>
@ -66,14 +68,16 @@ const NewHost = ({ onOk }) => {
createdAt: false,
_reference: false,
updatedAt: false,
operatingSystem: false,
'deviceInfo.os.platform': false,
'deviceInfo.os': false,
'deviceInfo.os.hostname': false,
'deviceInfo.cpu': false,
'deviceInfo.cpu.model': false,
'deviceInfo.user.username': false,
'deviceInfo.user.homedir': false,
'deviceInfo.user.shell': false,
'deviceInfo.process.nodeVersion': false,
'deviceInfo.process.execPath': false,
files: false,
connectedAt: false,
online: false

View File

@ -81,6 +81,37 @@ const applyComputedEntries = (base, entries = []) => {
return result
}
const collectModelPropertyPaths = (modelDefinition) => {
const paths = []
const visit = (properties) => {
;(properties || []).forEach((property) => {
if (property?.name) {
paths.push(property.name)
}
if (Array.isArray(property?.properties) && property.properties.length > 0) {
visit(property.properties)
}
})
}
visit(modelDefinition?.properties)
return paths
}
const pathToString = (path) =>
Array.isArray(path) ? path.join('.') : String(path)
// Computed display fields (e.g. deviceInfo.cpu) must not replace parent objects
// when sibling nested properties exist (e.g. deviceInfo.cpu.model).
const hasNestedPropertyPaths = (propertyPath, allPropertyPaths) => {
const pathStr = pathToString(propertyPath)
const prefix = `${pathStr}.`
return allPropertyPaths.some(
(name) => name !== pathStr && name.startsWith(prefix)
)
}
const getObjectChildrenNames = (modelDefinition) =>
(modelDefinition?.properties || [])
.filter((property) => property?.type === 'objectChildren' && property?.name)
@ -301,6 +332,7 @@ const ObjectForm = forwardRef(
}
const computedEntries = []
const allPropertyPaths = collectModelPropertyPaths(modelDefinition)
const processProperty = (property, parentPath = []) => {
if (!property?.name) return
@ -320,12 +352,18 @@ const ObjectForm = forwardRef(
try {
const computedValue = property.value(scopeData || {})
if (computedValue !== undefined) {
computedEntries.push({
namePath: propertyPath,
value: computedValue
})
// Update workingData so subsequent properties can use this value
set(workingData, propertyPath, computedValue)
const preserveNestedObject = hasNestedPropertyPaths(
propertyPath,
allPropertyPaths
)
if (!preserveNestedObject) {
computedEntries.push({
namePath: propertyPath,
value: computedValue
})
// Update workingData so subsequent properties can use this value
set(workingData, propertyPath, computedValue)
}
}
} catch (error) {
console.warn(

View File

@ -100,7 +100,7 @@ const ObjectInfo = ({
parentData={parentData}
showSince={true}
useFormItem={isControlled ? false : objectPropertyProps.useFormItem}
value={isControlled ? objectData?.[item.name] : undefined}
value={isControlled ? objectData?.[item.name] : item.value}
modelType={type}
onChange={
isControlled

View File

@ -38,7 +38,11 @@ export const Host = {
label: 'New Host',
icon: PlusIcon,
content: (objectData, { onOk } = {}) => {
return createElement(NewHost, { defaultValues: objectData, onOk, reset: true })
return createElement(NewHost, {
defaultValues: objectData,
onOk,
reset: true
})
}
},
{
@ -213,6 +217,14 @@ export const Host = {
columnWidth: 125
},
{
name: 'deviceInfo.os.platform',
label: 'Platform',
type: 'text',
required: false,
readOnly: true,
columnWidth: 120
},
{
name: 'deviceInfo.os',
label: 'Operating System',
@ -278,6 +290,14 @@ export const Host = {
readOnly: true,
columnWidth: 200
},
{
name: 'deviceInfo.user.shell',
label: 'User Shell',
type: 'text',
required: false,
readOnly: true,
columnWidth: 180
},
{
name: 'deviceInfo.process.nodeVersion',
label: 'NodeJS Version',
@ -286,6 +306,14 @@ export const Host = {
readOnly: true,
columnWidth: 150
},
{
name: 'deviceInfo.process.execPath',
label: 'NodeJS Exec Path',
type: 'text',
required: false,
readOnly: true,
columnWidth: 300
},
{
name: 'tags',
label: 'Tags',