Security hardening, multi-site portability, and a documentation and API-surface sweep

This commit is contained in:
2026-08-14 16:28:34 -04:00
parent 72f8da0e58
commit 265ca2eb01
76 changed files with 3684 additions and 811 deletions

View File

@@ -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([])

View File

@@ -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() }}-&lt;name&gt;</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
}