Add print badges, pagination, route splitting, JWT auth fixes, and list page alignment

- Fix equipment badge barcode not rendering (loading race condition)
- Fix printer QR code not rendering on initial load (same race condition)
- Add model image to equipment badge via imageurl from Model table
- Fix white-on-white machine number text on badge, tighten barcode spacing
- Add PaginationBar component used across all list pages
- Split monolithic router into per-plugin route modules
- Fix 25 GET API endpoints returning 401 (jwt_required -> optional=True)
- Align list page columns across Equipment, PCs, and Network pages
- Add print views: EquipmentBadge, PrinterQRSingle, PrinterQRBatch, USBLabelBatch
- Add PC Relationships report, migration docs, and CLAUDE.md project guide
- Various plugin model, API, and frontend refinements

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-02-04 07:32:44 -05:00
parent c4bfdc2db2
commit 9efdb5f52d
89 changed files with 3951 additions and 1138 deletions

View File

@@ -3,12 +3,12 @@
<div class="hero-card">
<div class="hero-image">
<div class="device-icon">
<span class="icon">{{ getDeviceIcon() }}</span>
<span class="icon"><component :is="getDeviceIcon()" :size="24" /></span>
</div>
</div>
<div class="hero-content">
<div class="hero-title-row">
<h1 class="hero-title">{{ device.network_device?.hostname || device.name || device.assetnumber }}</h1>
<h1 class="hero-title">{{ device.networkdevice?.hostname || device.name || device.assetnumber }}</h1>
<router-link
v-if="authStore.isAuthenticated"
:to="`/network/${deviceId}/edit`"
@@ -18,14 +18,14 @@
</router-link>
</div>
<div class="hero-meta">
<span class="badge" :class="getStatusClass(device.status_name)">
{{ device.status_name || 'Unknown' }}
<span class="badge" :class="getStatusClass(device.statusname)">
{{ device.statusname || 'Unknown' }}
</span>
<span v-if="device.network_device?.networkdevicetype_name" class="meta-item">
{{ device.network_device.networkdevicetype_name }}
<span v-if="device.networkdevice?.networkdevicetypename" class="meta-item">
{{ device.networkdevice.networkdevicetypename }}
</span>
<span v-if="device.network_device?.vendor_name" class="meta-item">
{{ device.network_device.vendor_name }}
<span v-if="device.networkdevice?.vendorname" class="meta-item">
{{ device.networkdevice.vendorname }}
</span>
</div>
<div class="hero-details">
@@ -37,19 +37,19 @@
<span class="label">Serial</span>
<span class="value mono">{{ device.serialnumber }}</span>
</div>
<div class="detail-item" v-if="device.location_name">
<div class="detail-item" v-if="device.locationname">
<span class="label">Location</span>
<span class="value">{{ device.location_name }}</span>
<span class="value">{{ device.locationname }}</span>
</div>
<div class="detail-item" v-if="device.businessunit_name">
<div class="detail-item" v-if="device.businessunitname">
<span class="label">Business Unit</span>
<span class="value">{{ device.businessunit_name }}</span>
<span class="value">{{ device.businessunitname }}</span>
</div>
</div>
<div class="hero-features" v-if="device.network_device">
<span v-if="device.network_device.ispoe" class="feature-badge poe">PoE</span>
<span v-if="device.network_device.ismanaged" class="feature-badge managed">Managed</span>
<span v-if="device.network_device.portcount" class="feature-badge ports">{{ device.network_device.portcount }} Ports</span>
<div class="hero-features" v-if="device.networkdevice">
<span v-if="device.networkdevice.ispoe" class="feature-badge poe">PoE</span>
<span v-if="device.networkdevice.ismanaged" class="feature-badge managed">Managed</span>
<span v-if="device.networkdevice.portcount" class="feature-badge ports">{{ device.networkdevice.portcount }} Ports</span>
</div>
</div>
</div>
@@ -62,27 +62,27 @@
<div class="info-list">
<div class="info-row">
<span class="info-label">Hostname</span>
<span class="info-value mono">{{ device.network_device?.hostname || '-' }}</span>
<span class="info-value mono">{{ device.networkdevice?.hostname || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">Firmware Version</span>
<span class="info-value">{{ device.network_device?.firmwareversion || '-' }}</span>
<span class="info-value">{{ device.networkdevice?.firmwareversion || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">Port Count</span>
<span class="info-value">{{ device.network_device?.portcount || '-' }}</span>
<span class="info-value">{{ device.networkdevice?.portcount || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">Rack Unit</span>
<span class="info-value">{{ device.network_device?.rackunit || '-' }}</span>
<span class="info-value">{{ device.networkdevice?.rackunit || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">PoE Capable</span>
<span class="info-value">{{ device.network_device?.ispoe ? 'Yes' : 'No' }}</span>
<span class="info-value">{{ device.networkdevice?.ispoe ? 'Yes' : 'No' }}</span>
</div>
<div class="info-row">
<span class="info-label">Managed Device</span>
<span class="info-value">{{ device.network_device?.ismanaged ? 'Yes' : 'No' }}</span>
<span class="info-value">{{ device.networkdevice?.ismanaged ? 'Yes' : 'No' }}</span>
</div>
</div>
</div>
@@ -113,17 +113,17 @@
</div>
<div class="info-row">
<span class="info-label">Vendor</span>
<span class="info-value">{{ device.network_device?.vendor_name || '-' }}</span>
<span class="info-value">{{ device.networkdevice?.vendorname || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">Device Type</span>
<span class="info-value">{{ device.network_device?.networkdevicetype_name || '-' }}</span>
<span class="info-value">{{ device.networkdevice?.networkdevicetypename || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">Status</span>
<span class="info-value">
<span class="badge" :class="getStatusClass(device.status_name)">
{{ device.status_name || 'Unknown' }}
<span class="badge" :class="getStatusClass(device.statusname)">
{{ device.statusname || 'Unknown' }}
</span>
</span>
</div>
@@ -177,6 +177,7 @@
<script setup>
import { ref, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { Network, Router, Shield, Wifi, Camera, Server, Server as Rack, Globe } from 'lucide-vue-next'
import { useAuthStore } from '../../stores/auth'
import { networkApi } from '../../api'
import AssetRelationships from '../../components/AssetRelationships.vue'
@@ -207,15 +208,15 @@ async function loadDevice() {
}
function getDeviceIcon() {
const type = device.value?.network_device?.networkdevicetype_name?.toLowerCase() || ''
if (type.includes('switch')) return '⏛'
if (type.includes('router')) return '⇌'
if (type.includes('firewall')) return '🛡'
if (type.includes('access point') || type.includes('ap')) return '📶'
if (type.includes('camera')) return '📷'
if (type.includes('server')) return '🖥'
if (type.includes('idf') || type.includes('closet')) return '🗄'
return '🌐'
const type = device.value?.networkdevice?.networkdevicetypename?.toLowerCase() || ''
if (type.includes('switch')) return Network
if (type.includes('router')) return Router
if (type.includes('firewall')) return Shield
if (type.includes('access point') || type.includes('ap')) return Wifi
if (type.includes('camera')) return Camera
if (type.includes('server')) return Server
if (type.includes('idf') || type.includes('closet')) return Rack
return Globe
}
function getStatusClass(status) {
@@ -240,7 +241,7 @@ function formatDate(dateStr) {
}
async function confirmDelete() {
if (confirm(`Are you sure you want to delete ${device.value.network_device?.hostname || device.value.assetnumber}?`)) {
if (confirm(`Are you sure you want to delete ${device.value.networkdevice?.hostname || device.value.assetnumber}?`)) {
try {
await networkApi.delete(deviceId)
router.push('/network')