Normalize the asset detail card layout across all five pages
All checks were successful
CI / backend (push) Successful in 1m25s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s

One canonical card order applied to machines, PCs, printers, network,
and measuring tools: Identity -> type-specific -> status -> Location &
Organization -> domain -> Custom Fields -> Warranty -> Relationships ->
Notes -> audit footer, with a documenting comment on each page so they
stop drifting. The location card is Location & Organization everywhere;
the network detail page is rebuilt into the family (Asset Information
folded into Identity, Record Info retired for the standard audit
footer, a Location & Organization card added). Fixed two latent bugs
found in the process: printer detail had no audit footer, and the
network Record Info read datecreated/datemodified (not in the payload)
so its timestamps rendered blank - the footer now uses the correct
fields.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-07-12 14:13:15 -04:00
parent 275224822e
commit b2e1827e5d
6 changed files with 168 additions and 146 deletions

View File

@@ -10,6 +10,18 @@ ADR-007 and ADR-002.
## [Unreleased] ## [Unreleased]
### Changed
- Asset detail pages (machines, PCs, printers, network devices, measuring
tools) now share one canonical card skeleton: Identity -> type-specific ->
status -> Location & Organization -> domain -> Custom Fields -> Warranty ->
Relationships -> Notes -> audit footer. The location card reads "Location &
Organization" on every page. The network device page was rebuilt into the
family (its "Asset Information" folded into Identity, "Record Info" converted
to the standard audit footer). Printer Notes moved out of mid-page to
second-to-last and the printer gained an audit footer. Template reordering
only; no data or API changes.
### Fixed ### Fixed
- PC detail Installed Applications no longer 500s and silently disappears on - PC detail Installed Applications no longer 500s and silently disappears on

View File

@@ -72,6 +72,7 @@
</div> </div>
</div> </div>
<!-- Canonical card order: Identity -> type-specific -> status -> Location & Organization -> domain -> Custom Fields -> Warranty -> Relationships -> Notes -> audit footer -->
<!-- Main Content Grid --> <!-- Main Content Grid -->
<div class="content-grid"> <div class="content-grid">
<!-- Left Column --> <!-- Left Column -->
@@ -137,6 +138,35 @@
</div> </div>
</div> </div>
</div>
<!-- Right Column -->
<div class="content-column">
<!-- Location & Organization -->
<div class="section-card">
<h3 class="section-title">Location & Organization</h3>
<div class="info-list">
<div class="info-row">
<span class="info-label">Location</span>
<span class="info-value">
<LocationMapTooltip
v-if="machine.mapx != null && machine.mapy != null"
:left="machine.mapx"
:top="machine.mapy"
:machineName="machine.assetnumber"
>
<span class="location-link">{{ machine.locationname || 'On Map' }}</span>
</LocationMapTooltip>
<span v-else>{{ machine.locationname || '-' }}</span>
</span>
</div>
<div class="info-row">
<span class="info-label">Business Unit</span>
<span class="info-value">{{ machine.businessunitname || '-' }}</span>
</div>
</div>
</div>
<!-- Machine Configuration --> <!-- Machine Configuration -->
<div class="section-card"> <div class="section-card">
<h3 class="section-title">Configuration</h3> <h3 class="section-title">Configuration</h3>
@@ -170,34 +200,6 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- Right Column -->
<div class="content-column">
<!-- Location & Organization -->
<div class="section-card">
<h3 class="section-title">Location & Organization</h3>
<div class="info-list">
<div class="info-row">
<span class="info-label">Location</span>
<span class="info-value">
<LocationMapTooltip
v-if="machine.mapx != null && machine.mapy != null"
:left="machine.mapx"
:top="machine.mapy"
:machineName="machine.assetnumber"
>
<span class="location-link">{{ machine.locationname || 'On Map' }}</span>
</LocationMapTooltip>
<span v-else>{{ machine.locationname || '-' }}</span>
</span>
</div>
<div class="info-row">
<span class="info-label">Business Unit</span>
<span class="info-value">{{ machine.businessunitname || '-' }}</span>
</div>
</div>
</div>
<!-- Custom Fields --> <!-- Custom Fields -->
<CustomFieldsSection :assetid="machine.assetid" /> <CustomFieldsSection :assetid="machine.assetid" />

View File

@@ -48,6 +48,7 @@
</div> </div>
</div> </div>
<!-- Canonical card order: Identity -> type-specific -> status -> Location & Organization -> domain -> Custom Fields -> Warranty -> Relationships -> Notes -> audit footer -->
<!-- Main Content Grid --> <!-- Main Content Grid -->
<div class="content-grid"> <div class="content-grid">
<!-- Left Column --> <!-- Left Column -->

View File

@@ -59,56 +59,12 @@
</div> </div>
</div> </div>
<!-- Canonical card order: Identity -> type-specific -> status -> Location & Organization -> domain -> Custom Fields -> Warranty -> Relationships -> Notes -> audit footer -->
<div class="content-grid"> <div class="content-grid">
<div class="content-column"> <div class="content-column">
<!-- Network Info --> <!-- Identity -->
<div class="section-card"> <div class="section-card">
<h3 class="section-title">Network Information</h3> <h3 class="section-title">Identity</h3>
<div class="info-list">
<div class="info-row">
<span class="info-label">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.networkdevice?.firmwareversion || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">Port Count</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.networkdevice?.rackunit || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">PoE Capable</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.networkdevice?.ismanaged ? 'Yes' : 'No' }}</span>
</div>
</div>
</div>
<!-- Custom Fields -->
<CustomFieldsSection :assetid="device.assetid" />
<!-- Warranty -->
<WarrantyPanel :assetid="device.assetid" :items="warranties" />
<!-- Notes -->
<div class="section-card" v-if="device.notes">
<h3 class="section-title">Notes</h3>
<div class="notes-content">{{ device.notes }}</div>
</div>
</div>
<div class="content-column">
<!-- Asset Info -->
<div class="section-card">
<h3 class="section-title">Asset Information</h3>
<div class="info-list"> <div class="info-list">
<div class="info-row"> <div class="info-row">
<span class="info-label">Asset Number</span> <span class="info-label">Asset Number</span>
@@ -138,16 +94,62 @@
<span class="info-label">Device Type</span> <span class="info-label">Device Type</span>
<span class="info-value">{{ device.networkdevice?.networkdevicetypename || '-' }}</span> <span class="info-value">{{ device.networkdevice?.networkdevicetypename || '-' }}</span>
</div> </div>
</div>
</div>
<!-- Network Information (type-specific) -->
<div class="section-card">
<h3 class="section-title">Network Information</h3>
<div class="info-list">
<div class="info-row"> <div class="info-row">
<span class="info-label">Status</span> <span class="info-label">Hostname</span>
<span class="info-value"> <span class="info-value mono">{{ device.networkdevice?.hostname || '-' }}</span>
<span class="badge" :style="colorStyle(device.statuscolor)"> </div>
{{ device.statusname || 'Unknown' }} <div class="info-row">
</span> <span class="info-label">Firmware Version</span>
</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.networkdevice?.portcount || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">Rack Unit</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.networkdevice?.ispoe ? 'Yes' : 'No' }}</span>
</div>
<div class="info-row">
<span class="info-label">Managed Device</span>
<span class="info-value">{{ device.networkdevice?.ismanaged ? 'Yes' : 'No' }}</span>
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="content-column">
<!-- Location & Organization -->
<div class="section-card">
<h3 class="section-title">Location & Organization</h3>
<div class="info-list">
<div class="info-row">
<span class="info-label">Location</span>
<span class="info-value">{{ device.locationname || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">Business Unit</span>
<span class="info-value">{{ device.businessunitname || '-' }}</span>
</div>
</div>
</div>
<!-- Custom Fields -->
<CustomFieldsSection :assetid="device.assetid" />
<!-- Warranty -->
<WarrantyPanel :assetid="device.assetid" :items="warranties" />
<!-- Relationships --> <!-- Relationships -->
<AssetRelationships <AssetRelationships
@@ -155,19 +157,10 @@
:assetId="device.assetid" :assetId="device.assetid"
/> />
<!-- Audit Info --> <!-- Notes -->
<div class="section-card audit-card"> <div class="section-card" v-if="device.notes">
<h3 class="section-title">Record Info</h3> <h3 class="section-title">Notes</h3>
<div class="info-list"> <div class="notes-content">{{ device.notes }}</div>
<div class="info-row" v-if="device.datecreated">
<span class="info-label">Created</span>
<span class="info-value">{{ formatDate(device.datecreated) }}</span>
</div>
<div class="info-row" v-if="device.datemodified">
<span class="info-label">Last Modified</span>
<span class="info-value">{{ formatDate(device.datemodified) }}</span>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -184,6 +177,12 @@
Delete Device Delete Device
</button> </button>
</div> </div>
<!-- Audit Footer -->
<div class="audit-footer">
<span>Created {{ formatDate(device.createddate) }}<template v-if="device.createdby"> by {{ device.createdby }}</template></span>
<span>Modified {{ formatDate(device.modifieddate) }}<template v-if="device.modifiedby"> by {{ device.modifiedby }}</template></span>
</div>
</div> </div>
<div v-else-if="loading" class="loading-container"> <div v-else-if="loading" class="loading-container">

View File

@@ -49,6 +49,7 @@
</div> </div>
</div> </div>
<!-- Canonical card order: Identity -> type-specific -> status -> Location & Organization -> domain -> Custom Fields -> Warranty -> Relationships -> Notes -> audit footer -->
<!-- Main Content Grid --> <!-- Main Content Grid -->
<div class="content-grid"> <div class="content-grid">
<!-- Left Column --> <!-- Left Column -->
@@ -118,6 +119,25 @@
<span v-if="!(computer.accessmethods || []).length" class="muted">None configured</span> <span v-if="!(computer.accessmethods || []).length" class="muted">None configured</span>
</div> </div>
</div> </div>
<!-- Status / Check-in -->
<div class="section-card">
<h3 class="section-title">Status</h3>
<div class="info-list">
<div class="info-row">
<span class="info-label">Logged In User</span>
<span class="info-value">{{ computer.computer?.loggedinuser || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">Last Reported</span>
<span class="info-value">{{ computer.computer?.lastreporteddate ? formatDate(computer.computer.lastreporteddate) : 'Never' }}</span>
</div>
<div class="info-row">
<span class="info-label">Last Boot</span>
<span class="info-value">{{ computer.computer?.lastboottime ? formatDate(computer.computer.lastboottime) : '-' }}</span>
</div>
</div>
</div>
</div> </div>
<!-- Right Column --> <!-- Right Column -->
@@ -140,28 +160,9 @@
<p v-else class="muted">No network addresses on record</p> <p v-else class="muted">No network addresses on record</p>
</div> </div>
<!-- Status / Check-in --> <!-- Location & Organization -->
<div class="section-card"> <div class="section-card">
<h3 class="section-title">Status</h3> <h3 class="section-title">Location & Organization</h3>
<div class="info-list">
<div class="info-row">
<span class="info-label">Logged In User</span>
<span class="info-value">{{ computer.computer?.loggedinuser || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">Last Reported</span>
<span class="info-value">{{ computer.computer?.lastreporteddate ? formatDate(computer.computer.lastreporteddate) : 'Never' }}</span>
</div>
<div class="info-row">
<span class="info-label">Last Boot</span>
<span class="info-value">{{ computer.computer?.lastboottime ? formatDate(computer.computer.lastboottime) : '-' }}</span>
</div>
</div>
</div>
<!-- Location -->
<div class="section-card">
<h3 class="section-title">Location</h3>
<div class="info-list"> <div class="info-list">
<div class="info-row"> <div class="info-row">
<span class="info-label">Location</span> <span class="info-label">Location</span>

View File

@@ -57,6 +57,7 @@
</div> </div>
</div> </div>
<!-- Canonical card order: Identity -> type-specific -> status -> Location & Organization -> domain -> Custom Fields -> Warranty -> Relationships -> Notes -> audit footer -->
<!-- Main Content Grid --> <!-- Main Content Grid -->
<div class="content-grid"> <div class="content-grid">
<!-- Left Column --> <!-- Left Column -->
@@ -126,28 +127,29 @@
</div> </div>
</div> </div>
</div> </div>
<!-- Custom Fields -->
<CustomFieldsSection :assetid="printer.assetid" />
<!-- Warranty -->
<WarrantyPanel :assetid="printer.assetid" :items="warranties" />
<!-- All relationships (defaultprinter, connectedto, ...) -->
<AssetRelationships v-if="printer.assetid" :assetId="printer.assetid" />
<!-- Notes -->
<div class="section-card" v-if="printer.notes">
<h3 class="section-title">Notes</h3>
<p class="notes-text">{{ printer.notes }}</p>
</div>
</div> </div>
<!-- Right Column --> <!-- Right Column -->
<div class="content-column"> <div class="content-column">
<!-- Location --> <!-- Network -->
<div class="section-card" v-if="printer.communications?.length">
<h3 class="section-title">Network</h3>
<div class="network-list">
<div v-for="comm in printer.communications" :key="comm.communicationid" class="network-item">
<div class="network-primary">
<span class="ip-address">{{ comm.ipaddress || comm.address || '-' }}</span>
<span v-if="comm.isprimary" class="primary-badge">Primary</span>
</div>
<div class="network-secondary" v-if="comm.macaddress">
<span class="mac-address">{{ comm.macaddress }}</span>
</div>
</div>
</div>
</div>
<!-- Location & Organization -->
<div class="section-card"> <div class="section-card">
<h3 class="section-title">Location</h3> <h3 class="section-title">Location & Organization</h3>
<div class="info-list"> <div class="info-list">
<div class="info-row"> <div class="info-row">
<span class="info-label">Map Location</span> <span class="info-label">Map Location</span>
@@ -170,20 +172,19 @@
</div> </div>
</div> </div>
<!-- Network --> <!-- Custom Fields -->
<div class="section-card" v-if="printer.communications?.length"> <CustomFieldsSection :assetid="printer.assetid" />
<h3 class="section-title">Network</h3>
<div class="network-list"> <!-- Warranty -->
<div v-for="comm in printer.communications" :key="comm.communicationid" class="network-item"> <WarrantyPanel :assetid="printer.assetid" :items="warranties" />
<div class="network-primary">
<span class="ip-address">{{ comm.ipaddress || comm.address || '-' }}</span> <!-- All relationships (defaultprinter, connectedto, ...) -->
<span v-if="comm.isprimary" class="primary-badge">Primary</span> <AssetRelationships v-if="printer.assetid" :assetId="printer.assetid" />
</div>
<div class="network-secondary" v-if="comm.macaddress"> <!-- Notes -->
<span class="mac-address">{{ comm.macaddress }}</span> <div class="section-card" v-if="printer.notes">
</div> <h3 class="section-title">Notes</h3>
</div> <p class="notes-text">{{ printer.notes }}</p>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -261,6 +262,12 @@
</table> </table>
</div> </div>
</div> </div>
<!-- Audit Footer -->
<div class="audit-footer">
<span>Created {{ formatDate(printer.createddate) }}<template v-if="printer.createdby"> by {{ printer.createdby }}</template></span>
<span>Modified {{ formatDate(printer.modifieddate) }}<template v-if="printer.modifiedby"> by {{ printer.modifiedby }}</template></span>
</div>
</template> </template>
<div v-else class="card"> <div v-else class="card">