Resolve an asset type by name, and send a map position with its level
Wave 5, the asset forms. Both defects are the same mistake in different clothes: a form deciding something it should have been told. Four forms carried a hardcoded asset-type id - const COMPUTER_ASSETTYPEID = 2, and the same for machines, printers and network devices - to tell CustomFieldsInputs which type's fields to render. Those ids come from seed order. On the instance they were written against, computer was 2; on a freshly seeded database computer is 1 and 2 is network_device. So the PC form asked for another type's custom fields, rendered them, and dropped everything typed into them behind a success toast, because the values were keyed to a type the record does not have. Verified in a browser against a correctly built dist: the form now requests assettypeid=1 on an instance where computer is 1. The name is the stable thing - assettype is a fixed vocabulary and the API already publishes the mapping - so composables/assetTypeIds.js resolves it once per page load. A failed load leaves it undefined, which renders no custom-field panel: a missing panel is honest, a guessed id is what this replaces. Three of the five forms that emit a map position sent mapx and mapy without levelid. ADR-017 is explicit that a position without its level cannot be drawn on the right floor plan, so a picked pin saved a null level and rendered nowhere - while the form displayed "Position: x, y" back, because it kept the value it had never sent. The ADR-017 gate should have caught that and did not: it globbed '*.py' only, so it policed what the server emits and never what the browser sends. It now covers .vue and .js too, and accepts both quoting styles. Verified by planting: drop levelid from PCForm again and the gate names that exact line. Five composable tests, including a fixture with a DIFFERENT seed order - the case the constants got wrong. Still open from this area, and deliberately not in this commit: the PC form writes an INHERITED location back as the record's own. Fixing it needs the API to distinguish an owned locationid from a derived one, which is a contract change rather than a form fix. Scope: product
This commit is contained in:
@@ -373,6 +373,7 @@ import CustomFieldsInputs from '@/components/CustomFieldsInputs.vue'
|
||||
import PrinterAssignmentPicker from '@/components/PrinterAssignmentPicker.vue'
|
||||
import { currentTheme } from '@/stores/theme'
|
||||
import { useIdentifierFlags } from '@/composables/identifierSettings'
|
||||
import { loadAssetTypeIds, assetTypeId } from '@/composables/assetTypeIds'
|
||||
import { isPluginEnabled, loadEnabledPlugins } from '@/composables/enabledPlugins'
|
||||
import { useToast } from '@/composables/toast'
|
||||
import { apiError } from '@/utils/apiError'
|
||||
@@ -390,9 +391,11 @@ const isEdit = computed(() => !!route.params.id)
|
||||
// falls back to the PCs list.
|
||||
const returnTo = computed(() => (route.query.from === 'fleet' ? '/endpoints' : '/pcs'))
|
||||
|
||||
// Seeded asset-type id for computers (see /api/assets/types). Custom-field
|
||||
// values are keyed by the underlying asset id, captured on load / create.
|
||||
const COMPUTER_ASSETTYPEID = 2
|
||||
// Resolved by NAME at load, never hardcoded: the id comes from seed
|
||||
// order, so the constant that used to live here pointed at a different
|
||||
// asset type on a differently-seeded site - the form then rendered THAT
|
||||
// type's custom fields and dropped whatever was typed into them.
|
||||
const COMPUTER_ASSETTYPEID = computed(() => assetTypeId('computer'))
|
||||
const customFieldsRef = ref(null)
|
||||
const printerPickerRef = ref(null)
|
||||
const currentAssetId = ref(null)
|
||||
@@ -519,6 +522,7 @@ const filteredVendors = computed(() => {
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
await loadAssetTypeIds()
|
||||
try {
|
||||
// Load reference data
|
||||
// perpage 100 so dropdowns aren't truncated to the default 20-row page
|
||||
@@ -649,7 +653,13 @@ async function savePC() {
|
||||
notes: form.value.notes || null,
|
||||
ipaddress: form.value.ipaddress || null,
|
||||
mapx: form.value.mapx,
|
||||
mapy: form.value.mapy
|
||||
mapy: form.value.mapy,
|
||||
// ADR-017: a position without its level cannot be drawn on the right
|
||||
// floor plan. The picker sets all three and this payload sent two, so a
|
||||
// picked pin saved a levelid of null and then rendered nowhere - the
|
||||
// form even showed "Position: x, y" back, because it kept the value it
|
||||
// had never sent.
|
||||
levelid: form.value.levelid
|
||||
}
|
||||
// only set display name when an alias is given, so we don't clobber it
|
||||
if (form.value.alias) {
|
||||
|
||||
@@ -416,6 +416,7 @@ import CustomFieldsInputs from '@/components/CustomFieldsInputs.vue'
|
||||
import PrinterAssignmentPicker from '@/components/PrinterAssignmentPicker.vue'
|
||||
import { currentTheme } from '@/stores/theme'
|
||||
import { useIdentifierFlags } from '@/composables/identifierSettings'
|
||||
import { loadAssetTypeIds, assetTypeId } from '@/composables/assetTypeIds'
|
||||
import { apiError } from '@/utils/apiError'
|
||||
import { planControllingPcChange } from './controllingPc'
|
||||
|
||||
@@ -426,8 +427,11 @@ const router = useRouter()
|
||||
|
||||
const isEdit = computed(() => !!route.params.id)
|
||||
|
||||
// Seeded asset-type id for machines (see /api/assets/types).
|
||||
const MACHINE_ASSETTYPEID = 1
|
||||
// Resolved by NAME at load, never hardcoded: the id comes from seed
|
||||
// order, so the constant that used to live here pointed at a different
|
||||
// asset type on a differently-seeded site - the form then rendered THAT
|
||||
// type's custom fields and dropped whatever was typed into them.
|
||||
const MACHINE_ASSETTYPEID = computed(() => assetTypeId('machine'))
|
||||
const customFieldsRef = ref(null)
|
||||
const printerPickerRef = ref(null)
|
||||
const currentAssetId = ref(null)
|
||||
@@ -571,6 +575,7 @@ watch(() => form.value.controllervendorid, (newVal, oldVal) => {
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
await loadAssetTypeIds()
|
||||
try {
|
||||
// Load reference data in parallel
|
||||
const [typesRes, statusRes, vendorRes, locRes, allModels, buRes, pcsRes, relTypesRes] = await Promise.all([
|
||||
@@ -763,7 +768,13 @@ async function saveMachine() {
|
||||
maintenanceintervaldays: form.value.maintenanceintervaldays || null,
|
||||
notes: form.value.notes || null,
|
||||
mapx: form.value.mapx,
|
||||
mapy: form.value.mapy
|
||||
mapy: form.value.mapy,
|
||||
// ADR-017: a position without its level cannot be drawn on the right
|
||||
// floor plan. The picker sets all three and this payload sent two, so a
|
||||
// picked pin saved a levelid of null and then rendered nowhere - the
|
||||
// form even showed "Position: x, y" back, because it kept the value it
|
||||
// had never sent.
|
||||
levelid: form.value.levelid
|
||||
}
|
||||
|
||||
let savedMachine
|
||||
|
||||
@@ -334,6 +334,7 @@ import { loadMapConfig, levelOptions, levelName, state as mapConfig }
|
||||
import Modal from '@/components/Modal.vue'
|
||||
import { currentTheme } from '@/stores/theme'
|
||||
import { useIdentifierFlags } from '@/composables/identifierSettings'
|
||||
import { loadAssetTypeIds, assetTypeId } from '@/composables/assetTypeIds'
|
||||
import { apiError } from '@/utils/apiError'
|
||||
|
||||
const { isEnabled } = useIdentifierFlags()
|
||||
@@ -350,8 +351,11 @@ const router = useRouter()
|
||||
const deviceId = route.params.id
|
||||
const isEdit = computed(() => !!deviceId)
|
||||
|
||||
// Seeded asset-type id for network devices (see /api/assets/types).
|
||||
const NETWORK_ASSETTYPEID = 3
|
||||
// Resolved by NAME at load, never hardcoded: the id comes from seed
|
||||
// order, so the constant that used to live here pointed at a different
|
||||
// asset type on a differently-seeded site - the form then rendered THAT
|
||||
// type's custom fields and dropped whatever was typed into them.
|
||||
const NETWORK_ASSETTYPEID = computed(() => assetTypeId('network_device'))
|
||||
const customFieldsRef = ref(null)
|
||||
const currentAssetId = ref(null)
|
||||
|
||||
@@ -424,6 +428,7 @@ const saving = ref(false)
|
||||
const error = ref('')
|
||||
|
||||
onMounted(async () => {
|
||||
await loadAssetTypeIds()
|
||||
await Promise.all([
|
||||
loadDeviceTypes(),
|
||||
loadVendors(),
|
||||
|
||||
@@ -336,6 +336,7 @@ import Modal from '@/components/Modal.vue'
|
||||
import CustomFieldsInputs from '@/components/CustomFieldsInputs.vue'
|
||||
import { currentTheme } from '@/stores/theme'
|
||||
import { useIdentifierFlags } from '@/composables/identifierSettings'
|
||||
import { loadAssetTypeIds, assetTypeId } from '@/composables/assetTypeIds'
|
||||
import { apiError } from '@/utils/apiError'
|
||||
import { getPrinterHostnameTemplate } from '@/utils/siteSettings'
|
||||
|
||||
@@ -346,8 +347,11 @@ const router = useRouter()
|
||||
|
||||
const isEdit = computed(() => !!route.params.id)
|
||||
|
||||
// Seeded asset-type id for printers (see /api/assets/types).
|
||||
const PRINTER_ASSETTYPEID = 4
|
||||
// Resolved by NAME at load, never hardcoded: the id comes from seed
|
||||
// order, so the constant that used to live here pointed at a different
|
||||
// asset type on a differently-seeded site - the form then rendered THAT
|
||||
// type's custom fields and dropped whatever was typed into them.
|
||||
const PRINTER_ASSETTYPEID = computed(() => assetTypeId('printer'))
|
||||
const customFieldsRef = ref(null)
|
||||
const currentAssetId = ref(null)
|
||||
const manualHostname = ref(false)
|
||||
@@ -542,6 +546,7 @@ function onWindowsNameInput() {
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadAssetTypeIds()
|
||||
try {
|
||||
// Load reference data (models paged in full via listAll, see api/index.js)
|
||||
// perpage 100 so dropdowns aren't truncated to the default 20-row page
|
||||
@@ -686,7 +691,13 @@ async function savePrinter() {
|
||||
isduplex: !!form.value.isduplex,
|
||||
isnetwork: !!form.value.isnetwork,
|
||||
mapx: form.value.mapx,
|
||||
mapy: form.value.mapy
|
||||
mapy: form.value.mapy,
|
||||
// ADR-017: a position without its level cannot be drawn on the right
|
||||
// floor plan. The picker sets all three and this payload sent two, so a
|
||||
// picked pin saved a levelid of null and then rendered nowhere - the
|
||||
// form even showed "Position: x, y" back, because it kept the value it
|
||||
// had never sent.
|
||||
levelid: form.value.levelid
|
||||
}
|
||||
// only set the display name when an alias is given, so we don't clobber it
|
||||
if (form.value.alias) {
|
||||
|
||||
Reference in New Issue
Block a user