Consolidate asset relationships onto the shared card
All checks were successful
CI / backend (push) Successful in 25s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 7s

The curated Controlled Machines (PC detail) and Connected PC (machine
detail) cards duplicated what the generic relationships card already
shows with type badges and inline add/remove - and hid every
non-controls relationship type. Removed the curated cards and their
plumbing (~105 lines of orphaned CSS included); the MachineForm
controlling-PC picker stays as the write path. Also theme-variable
fixes on the shared card styling.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-07-11 19:16:03 -04:00
parent 48d3160bc5
commit 02d8983195
3 changed files with 7 additions and 241 deletions

View File

@@ -1137,26 +1137,7 @@ td.actions {
font-style: italic;
}
/* Connected Device */
.connected-device {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
}
.device-link {
display: flex;
align-items: center;
gap: 0.75rem;
text-decoration: none;
color: inherit;
}
.device-link:hover .device-name {
color: var(--info);
}
/* Device icon (network detail) */
.device-icon {
width: 44px;
height: 44px;
@@ -1173,76 +1154,6 @@ td.actions {
height: 24px;
}
.device-info {
display: flex;
flex-direction: column;
}
.device-name {
font-weight: 600;
font-size: 15px;
}
.device-alias {
font-size: 13px;
color: var(--text-light);
}
.connection-type {
padding: 0.25rem 0.5rem;
font-size: 11px;
background: rgba(4, 185, 98, 0.2);
color: var(--success);
border-radius: 0.25rem;
}
/* Equipment List */
.equipment-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.equipment-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 0.25rem;
text-decoration: none;
color: inherit;
transition: border-color 0.15s;
}
.equipment-item:hover {
border-color: var(--primary);
}
.equipment-info {
display: flex;
flex-direction: column;
}
.equipment-name {
font-weight: 500;
font-size: 14px;
}
.equipment-alias {
font-size: 13px;
color: var(--text-light);
}
.connection-tag {
padding: 0.2rem 0.5rem;
font-size: 11px;
background: rgba(4, 185, 98, 0.2);
color: var(--success);
border-radius: 0.25rem;
}
/* Network */
.network-list {
display: flex;

View File

@@ -183,30 +183,6 @@
</div>
</div>
<!-- Connected PC -->
<div class="section-card">
<h3 class="section-title">Connected PC</h3>
<div v-if="!controllingPc" class="empty-message">
No controlling PC assigned
</div>
<div v-else class="connected-device">
<router-link :to="`/pcs/${controllingPc.pluginid || controllingPc.assetid}`" class="device-link">
<div class="device-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect>
<line x1="8" y1="21" x2="16" y2="21"></line>
<line x1="12" y1="17" x2="12" y2="21"></line>
</svg>
</div>
<div class="device-info">
<span class="device-name">{{ controllingPc.assetnumber }}</span>
<span class="device-alias" v-if="controllingPc.name">{{ controllingPc.name }}</span>
</div>
</router-link>
<span class="connection-type">{{ controllingPc.relationshipType }}</span>
</div>
</div>
<!-- Custom Fields -->
<CustomFieldsSection :assetid="machine.assetid" />
@@ -238,9 +214,9 @@
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { ref, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { machinesApi, assetsApi } from '../../api'
import { machinesApi } from '../../api'
import LocationMapTooltip from '../../components/LocationMapTooltip.vue'
import CustomFieldsSection from '../../components/CustomFieldsSection.vue'
import WarrantyPanel from '../../components/WarrantyPanel.vue'
@@ -254,54 +230,13 @@ const { isEnabled } = useIdentifierFlags()
const loading = ref(true)
const machine = ref(null)
const { warranties, heroWarranty, warrantyDate } = useWarrantyBadge(() => machine.value?.assetid)
const relationships = ref({ incoming: [], outgoing: [] })
// type name is data, sites may seed 'controls' or 'Controls' - compare folded
function isControls(rel) {
return (rel.relationshiptypename || '').toLowerCase() === 'controls'
}
const controllingPc = computed(() => {
// For a machine, find a related computer in any "Controls" relationship
// Check both incoming (computer controls this) and outgoing (legacy data may have machine -> computer)
// First check incoming - computer as source controlling this machine
for (const rel of relationships.value.incoming || []) {
if (rel.sourceasset?.assettypename === 'computer' && isControls(rel)) {
return {
...rel.sourceasset,
relationshipType: rel.relationshiptypename
}
}
}
// Also check outgoing - legacy data may have machine -> computer Controls relationships
for (const rel of relationships.value.outgoing || []) {
if (rel.targetasset?.assettypename === 'computer' && isControls(rel)) {
return {
...rel.targetasset,
relationshipType: rel.relationshiptypename
}
}
}
return null
})
// relationships render via the shared AssetRelationships card
onMounted(async () => {
try {
const response = await machinesApi.get(route.params.id)
machine.value = response.data.data
// Load relationships using asset ID
if (machine.value?.assetid) {
try {
const relResponse = await assetsApi.getRelationships(machine.value.assetid)
relationships.value = relResponse.data.data || { incoming: [], outgoing: [] }
} catch (e) {
console.log('Relationships not available')
}
}
} catch (error) {
console.error('Error loading machine:', error)
} finally {

View File

@@ -180,28 +180,6 @@
</div>
</div>
<!-- Controlled Machines -->
<div class="section-card" v-if="controlledEquipment.length > 0">
<h3 class="section-title">Controlled Machines</h3>
<div class="equipment-list">
<router-link
v-for="item in controlledEquipment"
:key="item.relationshipid"
:to="`/machines/${item.pluginid || item.assetid}`"
class="equipment-item"
>
<div class="equipment-info">
<span class="equipment-name">{{ item.assetnumber }}</span>
<span class="equipment-alias" v-if="item.name">{{ item.name }}</span>
</div>
<div class="equipment-meta">
<span class="category-tag">{{ item.assettypename }}</span>
<span class="connection-tag">{{ item.relationshipType }}</span>
</div>
</router-link>
</div>
</div>
<!-- Installed Applications -->
<div class="section-card" v-if="installedApps.length > 0">
<h3 class="section-title">Installed Applications</h3>
@@ -254,10 +232,10 @@
</template>
<script setup>
import { ref, onMounted, computed } from 'vue'
import { ref, onMounted } from 'vue'
import { colorStyle } from "@/utils/colorStyle"
import { useRoute } from 'vue-router'
import { computersApi, applicationsApi, assetsApi } from '../../api'
import { computersApi, applicationsApi } from '../../api'
import { useWarrantyBadge } from '../../composables/warrantyBadge'
import LocationMapTooltip from '../../components/LocationMapTooltip.vue'
import CustomFieldsSection from '../../components/CustomFieldsSection.vue'
@@ -270,61 +248,18 @@ const { isEnabled } = useIdentifierFlags()
const loading = ref(true)
const computer = ref(null)
const relationships = ref({ incoming: [], outgoing: [] })
const installedApps = ref([])
// Warranty fetch + hero badge (shared across asset detail pages).
const { warranties, heroWarranty, warrantyDate: formatWarrantyDate } = useWarrantyBadge(() => computer.value?.assetid)
// type name is data, sites may seed 'controls' or 'Controls' - compare folded
function isControls(rel) {
return (rel.relationshiptypename || '').toLowerCase() === 'controls'
}
const controlledEquipment = computed(() => {
// For computers, find related equipment in any "Controls" relationship
const items = []
// Check outgoing - computer controls equipment
for (const rel of relationships.value.outgoing || []) {
if (rel.targetasset?.assettypename === 'machine' && isControls(rel)) {
items.push({
...rel.targetasset,
relationshipid: rel.relationshipid,
relationshipType: rel.relationshiptypename
})
}
}
// Also check incoming - legacy data may have equipment -> computer Controls relationships
for (const rel of relationships.value.incoming || []) {
if (rel.sourceasset?.assettypename === 'machine' && isControls(rel)) {
items.push({
...rel.sourceasset,
relationshipid: rel.relationshipid,
relationshipType: rel.relationshiptypename
})
}
}
return items
})
// relationships render via the shared AssetRelationships card
onMounted(async () => {
try {
const response = await computersApi.get(route.params.id)
computer.value = response.data.data
// Load relationships using asset ID
if (computer.value?.assetid) {
try {
const relResponse = await assetsApi.getRelationships(computer.value.assetid)
relationships.value = relResponse.data.data || { incoming: [], outgoing: [] }
} catch (e) {
console.log('Relationships not available')
}
}
// Load installed applications
try {
const appsResponse = await applicationsApi.getMachineApps(route.params.id)
@@ -434,21 +369,6 @@ function formatDate(dateStr) {
color: var(--text-light);
}
/* Equipment meta */
.equipment-meta {
display: flex;
gap: 0.5rem;
align-items: center;
}
.category-tag {
padding: 0.3rem 0.625rem;
font-size: 0.875rem;
background: var(--primary);
color: white;
border-radius: 5px;
}
/* Network details */
.network-details {
margin-top: 0.5rem;