Security hardening, multi-site portability, and a documentation and API-surface sweep
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