Enforcement reports with asset context and backup state; manifest editor spacing

This commit is contained in:
2026-08-12 16:35:46 -04:00
parent ee7f8640d1
commit e8f33cffa1
3 changed files with 256 additions and 12 deletions

View File

@@ -34,14 +34,49 @@
<table>
<thead>
<tr>
<th>Host</th><th>PC type</th><th>Received</th><th>Version</th>
<th>Host</th><th>Asset</th><th>PC type</th><th>Received</th><th>Version</th>
<th>Status</th><th>Installed</th><th>Skipped</th><th>Failed</th>
<th>Last check-in</th><th></th>
<th>Backup</th><th>Last check-in</th><th></th>
</tr>
</thead>
<tbody>
<tr v-for="report in reports" :key="report.reportid">
<td>{{ report.hostname }}</td>
<td class="host-cell">
<router-link v-if="report.computerid" :to="`/pcs/${report.computerid}`">
{{ report.hostname }}
</router-link>
<span v-else>{{ report.hostname }}</span>
<!-- Coordinates give the same floor-plan preview the asset's own
page uses. No coordinates, no icon - an icon that opened an
empty map would be worse than none. -->
<LocationMapTooltip
v-if="report.mapx != null && report.mapy != null"
:left="report.mapx" :top="report.mapy"
:machineName="report.hostname"
>
<span class="map-pin" :title="report.location || 'On the floor plan'">&#9678;</span>
</LocationMapTooltip>
</td>
<td class="asset-cell">
<!-- What this PC IS or DRIVES, whichever applies. A part-marker
or bay PC shows its machine; a measuring-tool PC its tool;
a display its role. -->
<router-link v-if="report.machineassetid" class="asset-chip"
:to="`/machines/${report.machineassetid}`"
title="Machine this PC controls">
{{ report.machinenumber }}
</router-link>
<router-link v-if="report.toolassetid" class="asset-chip"
:to="`/measuringtools/${report.toolassetid}`"
title="Measuring tool this PC controls">
{{ report.toolassetnumber }}
</router-link>
<span v-if="report.displayrole" class="asset-chip muted"
title="Display role">{{ report.displayrole }}</span>
<span v-if="!report.machineassetid && !report.toolassetid && !report.displayrole"
class="muted">{{ report.assetnumber || '-' }}</span>
<div v-if="report.location" class="asset-location muted">{{ report.location }}</div>
</td>
<td>{{ report.scopename }}</td>
<td>
<span class="badge" :class="report.receivedlatest ? 'badge-success' : 'badge-warning'">
@@ -53,12 +88,22 @@
<td>{{ report.installed }}</td>
<td class="muted">{{ report.skipped }}</td>
<td :class="{ 'fail-count': report.failed }">{{ report.failed }}</td>
<!-- Last CONFIRMED, not last changed: an unchanged config writes
no revision, so a stable machine is healthy with an old
revision. What matters is whether the backup still runs. -->
<td class="muted">
<template v-if="report.backuplastseen">
{{ formatDate(report.backuplastseen) }}
<div class="backup-kind">{{ report.backupkind }}</div>
</template>
<span v-else>-</span>
</td>
<td class="muted">{{ formatDate(report.lastcheckin || report.receivedat) }}</td>
<td class="actions">
<button class="btn btn-sm btn-secondary" @click="openDetail(report.reportid)">Detail</button>
</td>
</tr>
<tr v-if="!reports.length"><td colspan="10" class="empty">No reports yet.</td></tr>
<tr v-if="!reports.length"><td colspan="12" class="empty">No reports yet.</td></tr>
</tbody>
</table>
</div>
@@ -105,6 +150,7 @@
<script setup>
import { ref } from 'vue'
import api from '@/api'
import LocationMapTooltip from '@/components/LocationMapTooltip.vue'
const reports = ref([])
const detail = ref(null)
@@ -163,4 +209,17 @@ load()
padding: 0.25rem 0.5rem;
}
.modal-close:hover { color: var(--text); }
.host-cell { display: flex; align-items: center; gap: 0.35rem; }
.map-pin { cursor: pointer; color: var(--primary); font-size: 0.95rem; line-height: 1; }
.asset-cell { white-space: nowrap; }
.asset-chip {
display: inline-block;
font-size: 0.8rem;
padding: 0.05rem 0.4rem;
margin-right: 0.25rem;
border: 1px solid var(--border);
border-radius: 3px;
}
.asset-location { font-size: 0.75rem; margin-top: 0.15rem; }
.backup-kind { font-size: 0.72rem; }
</style>

View File

@@ -945,18 +945,41 @@ loadApplications()
/* Modals: wider entry/preview, still responsive (global max-width + width:100%). */
.modal-entry { max-width: min(1100px, 96vw); }
.toggle-all { font-weight: 400; font-size: 0.8rem; }
.entry-name { font-weight: 500; }
.entry-desc { font-size: 0.78rem; margin-top: 0.15rem; overflow-wrap: anywhere; }
.entry-name { font-weight: 500; line-height: 1.35; }
.entry-desc {
font-size: 0.78rem;
margin-top: 0.3rem;
line-height: 1.45;
overflow-wrap: anywhere;
}
.scope-summary { margin: 0 0 0.75rem 0; font-size: 0.85rem; }
/* Fixed layout so the Entry column flexes and the button columns never force
a horizontal scroll. */
.scope-detail .entries-table { width: 100%; table-layout: fixed; }
.entries-table th.col-order, .entries-table td.order-cell { width: 58px; }
.entries-table th.col-type { width: 64px; }
.entries-table th.col-actions, .entries-table td.row-actions { width: 128px; }
.order-cell { display: flex; flex-direction: column; gap: 0.2rem; }
.order-cell .btn { padding: 0.1rem 0.3rem; }
.row-actions { display: flex; gap: 0.3rem; flex-wrap: wrap; }
/* Every column was sized to the button text with nothing to spare, so the
action buttons wrapped out of their cell and landed on the description. The
widths below fit Edit + Delete on one line, and nowrap keeps them there. */
.entries-table th.col-order, .entries-table td.order-cell { width: 84px; }
.entries-table th.col-type { width: 76px; }
.entries-table th.col-actions, .entries-table td.row-actions { width: 172px; }
.entries-table td {
/* A row is two lines of text plus stacked buttons; without vertical padding
and top alignment the rows run into each other. */
padding: 0.6rem 0.6rem;
vertical-align: top;
}
.order-cell { display: flex; flex-direction: column; gap: 0.3rem; }
.order-cell .btn {
padding: 0.25rem 0.5rem;
width: 100%;
}
.row-actions {
display: flex;
gap: 0.4rem;
flex-wrap: nowrap;
white-space: nowrap;
}
.row-actions .btn { padding: 0.25rem 0.6rem; }
.help-panel { margin: 0 0 1rem 0; border: 1px solid var(--border);
border-radius: 6px; padding: 0.5rem 0.75rem; background: var(--bg-card); }
.help-panel summary { cursor: pointer; font-weight: 600; font-size: 0.9rem; }