network: generate a device's asset number instead of asking twice
Every network device on this fleet already follows one convention, applied by hand: AP-<name>, SW-<name>, SVR-<name>, IDF-<name>. 45 records, no exceptions. The create form demanded the asset number anyway, so the same value was typed twice and the convention held only as long as everyone remembered it. The prefix now lives on the device type, and a blank asset number is generated as <PREFIX>-<name>. Left explicit, an asset number always wins: a device carrying a real identifier of its own - a vendor tag, a controller name, a serial - keeps it. That is the platform rule, adopt where an identifier exists and derive only where none does. The prefix is NOT derived from the type name. "Access Point" and "Access Panel" both initialise to AP, and assetnumber is unique, so the second type would collide with the first on every device it created. It is nullable, so a type that wants no prefix generates the bare name rather than needing one invented. Names are sanitised before they reach a business key - the existing data already shows why, with IDF-Telco-Demarc-#1 carrying a '#' into an identifier. An existing prefix is never stacked: IDF-03 under type IDF stays IDF-03.
This commit is contained in:
@@ -12,15 +12,24 @@
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="assetnumber">Asset Number *</label>
|
||||
<!-- Not required on create: left blank it is generated from the
|
||||
device type's prefix and the name (AP-<name>). An explicit
|
||||
value always wins, because a device carrying a real
|
||||
identifier - vendor tag, controller name, serial - should
|
||||
keep it. Still locked on edit: other records join on this. -->
|
||||
<label for="assetnumber">Asset Number<span v-if="isEdit"> *</span></label>
|
||||
<input
|
||||
id="assetnumber"
|
||||
v-model="form.assetnumber"
|
||||
type="text"
|
||||
class="form-control"
|
||||
required
|
||||
:required="isEdit"
|
||||
:disabled="isEdit"
|
||||
:placeholder="generatedAssetNumber || 'leave blank to generate from the name'"
|
||||
/>
|
||||
<small class="form-hint" v-if="!isEdit && !form.assetnumber && generatedAssetNumber">
|
||||
Will be created as <code>{{ generatedAssetNumber }}</code>
|
||||
</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
@@ -343,6 +352,27 @@ const form = ref({
|
||||
})
|
||||
|
||||
const deviceTypes = ref([])
|
||||
|
||||
// Preview of what the backend will generate. Mirrors
|
||||
// plugins/network/services/assetnumbers.py - the server is authoritative and
|
||||
// re-derives it; this only shows the operator what they are about to get.
|
||||
const generatedAssetNumber = computed(() => {
|
||||
const cleaned = (value) => (value || '')
|
||||
.trim()
|
||||
.replace(/[\s_/\\]+/g, '-')
|
||||
.replace(/[^A-Za-z0-9.-]/g, '')
|
||||
.replace(/-{2,}/g, '-')
|
||||
.replace(/^-+|-+$/g, '')
|
||||
const name = cleaned(form.value.name)
|
||||
if (!name) return ''
|
||||
const found = deviceTypes.value.find(
|
||||
t => String(t.networkdevicetypeid) === String(form.value.networkdevicetypeid))
|
||||
const prefix = cleaned(found?.prefix).toUpperCase()
|
||||
if (!prefix) return name
|
||||
// never stack a prefix the name already carries (IDF-03 under type IDF)
|
||||
if (name.toUpperCase().startsWith(prefix + '-')) return name
|
||||
return `${prefix}-${name}`
|
||||
})
|
||||
const vendors = ref([])
|
||||
const models = ref([])
|
||||
const locations = ref([])
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Network Device Type</th>
|
||||
<th>Prefix</th>
|
||||
<th>Description</th>
|
||||
<th>Color</th>
|
||||
<th>Actions</th>
|
||||
@@ -22,6 +23,7 @@
|
||||
<tbody>
|
||||
<tr v-for="t in visibleItems" :key="t.networkdevicetypeid">
|
||||
<td>{{ t.networkdevicetype }}</td>
|
||||
<td><code v-if="t.prefix">{{ t.prefix }}-</code><span v-else class="muted">-</span></td>
|
||||
<td class="cell-truncate" :title="t.description">{{ t.description || '-' }}</td>
|
||||
<td><span class="badge" :style="colorStyle(t.color)">{{ t.color || 'auto' }}</span></td>
|
||||
<td class="actions">
|
||||
@@ -31,7 +33,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="visibleItems.length === 0">
|
||||
<td colspan="4" style="text-align: center; color: var(--text-light);">No network device types found</td>
|
||||
<td colspan="5" style="text-align: center; color: var(--text-light);">No network device types found</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -48,6 +50,21 @@
|
||||
<label>Network Device Type *</label>
|
||||
<input v-model="form.networkdevicetype" type="text" class="form-control" required />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<!-- Every device of this type gets <PREFIX>-<name> as its asset
|
||||
number when the number is left blank. Blank here = the bare
|
||||
name, so a type that does not want a prefix needs no
|
||||
invented one. NOT derived from the type name: "Access Point"
|
||||
and "Access Panel" both give AP, and asset numbers are
|
||||
unique, so the second type would collide with the first on
|
||||
every device. -->
|
||||
<label>Asset Number Prefix <span class="hint">(AP, SW, SVR; blank = use the name alone)</span></label>
|
||||
<input v-model="form.prefix" type="text" class="form-control" maxlength="12"
|
||||
placeholder="e.g. APNL" />
|
||||
<small class="form-hint" v-if="form.prefix">
|
||||
New devices will be numbered <code>{{ form.prefix.toUpperCase() }}-<name></code>
|
||||
</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Description</label>
|
||||
<textarea v-model="form.description" class="form-control" rows="3"></textarea>
|
||||
@@ -88,7 +105,7 @@ const showModal = ref(false)
|
||||
const editing = ref(null)
|
||||
const saving = ref(false)
|
||||
const error = ref('')
|
||||
const form = ref({ networkdevicetype: '', description: '', color: '', isactive: true })
|
||||
const form = ref({ networkdevicetype: '', prefix: '', description: '', color: '', isactive: true })
|
||||
|
||||
onMounted(loadData)
|
||||
|
||||
@@ -107,8 +124,8 @@ async function loadData() {
|
||||
function openModal(item = null) {
|
||||
editing.value = item
|
||||
form.value = item
|
||||
? { networkdevicetype: item.networkdevicetype || '', description: item.description || '', color: item.color || '', isactive: item.isactive !== false }
|
||||
: { networkdevicetype: '', description: '', color: '', isactive: true }
|
||||
? { networkdevicetype: item.networkdevicetype || '', prefix: item.prefix || '', description: item.description || '', color: item.color || '', isactive: item.isactive !== false }
|
||||
: { networkdevicetype: '', prefix: '', description: '', color: '', isactive: true }
|
||||
error.value = ''
|
||||
showModal.value = true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user