Enforcement reports with asset context and backup state; manifest editor spacing
This commit is contained in:
@@ -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'">◎</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>
|
||||
|
||||
@@ -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; }
|
||||
|
||||
Reference in New Issue
Block a user