Rebuild GE-Enforce editor: themed (Fable redesign) + semantics-aware targeting
Replace the generic field-dump editor with the Fable-coordinated redesign (stock .section-card / .settings-grid / .setting-row / .table-container / badge / btn primitives, opaque global .modal on --bg-card-solid, proper modal-header/body/footer), then layer the manifest semantics on top: - CONTEXTUAL targeting: an entry only shows the gates its scope actually uses, instead of every gate on every entry. Data-driven + scope-aware - common/preinstall (fleet-wide) show PC types; CMM shows the version gate; a scope whose entries use machine numbers (collections) shows those; hostnames show when used. A short explainer states why (a per-type manifest already runs only on its own type), and "Show all targeting options" reveals everything. Hidden gates keep their value on save (no data loss). - Preserves all functionality: type-switched payload blocks, detection reveal, structured InUseCheck rows (name/ExePath/timeout), LogFile, preinstall checkboxes, simulate, publish/versions/rollback, export. Entry modal widened to min(1100px,96vw). EnforcementReports adopts the themed redesign (filters, card > table-container, badge-mapped statuses, global modal) with the error handling kept. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,72 +1,102 @@
|
||||
<template>
|
||||
<div class="enforcement-reports">
|
||||
<div class="page-header"><h1>Enforcement Reports</h1></div>
|
||||
<div v-if="error" class="alert-error">{{ error }}</div>
|
||||
<p class="intro">
|
||||
<div class="page-header">
|
||||
<h2>Enforcement Reports</h2>
|
||||
</div>
|
||||
<p class="setting-description">
|
||||
Latest GE-Enforce result reported by each PC. "Received" means the PC picked
|
||||
up the current published manifest; status shows self-heal and failures.
|
||||
</p>
|
||||
|
||||
<div v-if="error" class="error-message">{{ error }}</div>
|
||||
|
||||
<div class="filters">
|
||||
<input v-model="filterHost" placeholder="Filter hostname" @keyup.enter="load" />
|
||||
<input v-model="filterScope" placeholder="Filter PC type" @keyup.enter="load" />
|
||||
<button class="btn" @click="load">Filter</button>
|
||||
<button class="btn" @click="clearFilters">Clear</button>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
v-model="filterHost"
|
||||
placeholder="Filter hostname"
|
||||
@keyup.enter="load"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
v-model="filterScope"
|
||||
placeholder="Filter PC type"
|
||||
@keyup.enter="load"
|
||||
/>
|
||||
<button class="btn btn-primary" @click="load">Filter</button>
|
||||
<button class="btn btn-secondary" @click="clearFilters">Clear</button>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="table-scroll">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Host</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>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="r in reports" :key="r.reportid">
|
||||
<td>{{ r.hostname }}</td>
|
||||
<td>{{ r.scopename }}</td>
|
||||
<td>
|
||||
<span class="badge" :class="r.receivedlatest ? 'badge-success' : 'badge-warning'">
|
||||
{{ r.receivedlatest ? 'yes' : 'behind' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="dim">{{ r.appliedversion ?? '-' }} / {{ r.latestversion ?? '-' }}</td>
|
||||
<td><span class="badge" :class="statusClass(r.status)">{{ r.status }}</span></td>
|
||||
<td>{{ r.installed }}</td>
|
||||
<td class="dim">{{ r.skipped }}</td>
|
||||
<td :class="{ 'fail-count': r.failed }">{{ r.failed }}</td>
|
||||
<td class="dim">{{ formatDate(r.lastcheckin || r.receivedat) }}</td>
|
||||
<td><button class="btn btn-small" @click="openDetail(r.reportid)">Detail</button></td>
|
||||
</tr>
|
||||
<tr v-if="!reports.length"><td colspan="10" class="empty">No reports yet.</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Host</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>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="report in reports" :key="report.reportid">
|
||||
<td>{{ report.hostname }}</td>
|
||||
<td>{{ report.scopename }}</td>
|
||||
<td>
|
||||
<span class="badge" :class="report.receivedlatest ? 'badge-success' : 'badge-warning'">
|
||||
{{ report.receivedlatest ? 'yes' : 'behind' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="muted">{{ report.appliedversion ?? '-' }} / {{ report.latestversion ?? '-' }}</td>
|
||||
<td><span class="badge" :class="statusClass(report.status)">{{ report.status }}</span></td>
|
||||
<td>{{ report.installed }}</td>
|
||||
<td class="muted">{{ report.skipped }}</td>
|
||||
<td :class="{ 'fail-count': report.failed }">{{ report.failed }}</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>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Detail modal -->
|
||||
<div v-if="detail" class="modal-overlay" @click.self="detail = null">
|
||||
<div class="modal modal-wide">
|
||||
<h2>{{ detail.hostname }} - {{ detail.scopename }}</h2>
|
||||
<p class="dim">Applied v{{ detail.appliedversion ?? '-' }} of v{{ detail.latestversion ?? '-' }};
|
||||
status {{ detail.status }}</p>
|
||||
<table>
|
||||
<thead><tr><th>Entry</th><th>Action</th><th>Self-heal</th><th>Exit</th><th>Message</th></tr></thead>
|
||||
<tbody>
|
||||
<tr v-for="(result, i) in detail.results" :key="i">
|
||||
<td>{{ result.entryname }}</td>
|
||||
<td><span class="badge" :class="actionClass(result.action)">{{ result.action }}</span></td>
|
||||
<td>{{ result.selfhealed ? 'yes' : '' }}</td>
|
||||
<td class="dim">{{ result.exitcode ?? '' }}</td>
|
||||
<td class="dim">{{ result.message }}</td>
|
||||
</tr>
|
||||
<tr v-if="!detail.results.length"><td colspan="5" class="empty">No per-entry detail.</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="modal-actions"><button class="btn" @click="detail = null">Close</button></div>
|
||||
<div class="modal modal-report">
|
||||
<div class="modal-header">
|
||||
<h3>{{ detail.hostname }} - {{ detail.scopename }}</h3>
|
||||
<button class="modal-close" @click="detail = null">x</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p class="setting-description">
|
||||
Applied v{{ detail.appliedversion ?? '-' }} of v{{ detail.latestversion ?? '-' }};
|
||||
status {{ detail.status }}
|
||||
</p>
|
||||
<div class="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Entry</th><th>Action</th><th>Self-heal</th><th>Exit</th><th>Message</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(result, index) in detail.results" :key="index">
|
||||
<td>{{ result.entryname }}</td>
|
||||
<td><span class="badge" :class="actionClass(result.action)">{{ result.action }}</span></td>
|
||||
<td>{{ result.selfhealed ? 'yes' : '' }}</td>
|
||||
<td class="muted">{{ result.exitcode ?? '' }}</td>
|
||||
<td class="muted">{{ result.message }}</td>
|
||||
</tr>
|
||||
<tr v-if="!detail.results.length"><td colspan="5" class="empty">No per-entry detail.</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" @click="detail = null">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -114,30 +144,18 @@ load()
|
||||
|
||||
<style scoped>
|
||||
.enforcement-reports { max-width: 1100px; }
|
||||
.enforcement-reports input, .enforcement-reports select {
|
||||
padding: 0.45rem 0.55rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font: inherit;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.enforcement-reports input:focus { outline: none; border-color: var(--primary); }
|
||||
.intro { color: var(--text-light); margin: 0 0 1rem 0; }
|
||||
.alert-error { background: var(--danger); color: white; padding: 0.5rem 0.75rem;
|
||||
border-radius: 4px; margin-bottom: 1rem; }
|
||||
.table-scroll { overflow-x: auto; }
|
||||
.filters { display: flex; gap: 0.5rem; margin-bottom: 1rem; }
|
||||
.dim { color: var(--text-light); }
|
||||
.muted { color: var(--text-light); }
|
||||
.fail-count { color: var(--danger); font-weight: 600; }
|
||||
.btn-small { padding: 0.15rem 0.5rem; font-size: 0.78rem; }
|
||||
.empty { color: var(--text-light); text-align: center; padding: 1rem; }
|
||||
.modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); display: flex;
|
||||
align-items: center; justify-content: center; z-index: 100; }
|
||||
.modal { background: var(--bg-card-solid); padding: 1.5rem; border-radius: 8px;
|
||||
border: 1px solid var(--border); box-shadow: 0 12px 40px rgba(0, 0, 0, 0.35);
|
||||
min-width: 340px; max-height: 90vh; overflow-y: auto; }
|
||||
.modal-wide { width: min(820px, 94vw); }
|
||||
.modal-actions { display: flex; justify-content: flex-end; margin-top: 0.75rem; }
|
||||
.modal-report { max-width: 640px; }
|
||||
.modal-close {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-light);
|
||||
font-size: 1.1rem;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
padding: 0.25rem 0.5rem;
|
||||
}
|
||||
.modal-close:hover { color: var(--text); }
|
||||
</style>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user