Make GE-Enforce editor more legible: plain-English entries + how-it-works panel
All checks were successful
CI / backend (push) Successful in 1m37s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s

- Each manifest entry now shows a one-line intent summary under its name
  (e.g. 'Installs eDNC; reinstalls if not detected; 2 PC types') instead of only
  the raw Type/Detection/Filters columns - turns jargon into what it actually does.
- A collapsible 'How this works' panel at the top gives the mental model in four
  sentences (desired state + self-heal, entries + detection, targeting, doc link).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-07-12 22:45:50 -04:00
parent 655fe1087f
commit ab0c9a454b

View File

@@ -8,6 +8,25 @@
then Publish to ship it to PCs. Nothing reaches a PC until you publish.
</p>
<details class="help-panel">
<summary>How this works</summary>
<div class="help-body">
<p><strong>A manifest is the desired state for a PC type</strong> - the
list of what should be installed. shopdb serves it; each PC checks
itself every cycle and re-installs anything that drifted (uninstalled,
corrupted, or overwritten). That is the "self-heal".</p>
<p><strong>Each entry</strong> is one action (install an app, copy a
file, write a registry value, run a script) plus a <em>detection</em>
rule that tells the PC whether it is already correct - if not, the
action runs. Entry order is execution order.</p>
<p><strong>Targeting</strong> narrows an entry: the fleet-wide
<code>common</code> manifest uses PC types to hit a subset; a per-type
manifest already only runs on its own type, so it shows just the niche
gate it needs (CMM version, a bay number, a subtype).</p>
<p>Full guide: <code>docs/GE-ENFORCE.md</code>.</p>
</div>
</details>
<div v-if="error" class="error-message">{{ error }}</div>
<div v-if="notice" class="settings-success">{{ notice }}</div>
@@ -151,7 +170,10 @@
@click="moveEntry(index, 1)"
>Down</button>
</td>
<td>{{ entry.Name }}</td>
<td>
{{ entry.Name }}
<div class="entry-desc muted">{{ describeEntry(entry) }}</div>
</td>
<td><span class="badge badge-info">{{ entry.Type }}</span></td>
<td class="muted">{{ entry.DetectionMethod || 'always' }}</td>
<td class="muted">{{ filterSummary(entry) }}</td>
@@ -749,6 +771,32 @@ function filterSummary(entry) {
if (entry._CmmVersion) parts.push(`cmm:${entry._CmmVersion}`)
return parts.join(' ') || '-'
}
// Plain-English one-liner: what this entry does, when it self-heals, who it
// applies to. Turns "eDNC | MSI | Registry | -" into intent an IT tech reads.
const ACTION_VERB = {
MSI: 'Installs', EXE: 'Installs', CMD: 'Runs', BAT: 'Runs',
PS1: 'Runs a script for', INF: 'Installs a driver for',
File: 'Copies a file for', Registry: 'Sets a registry value for',
}
function describeEntry(entry) {
let text = `${ACTION_VERB[entry.Type] || 'Applies'} ${entry.Name}`
const method = entry.DetectionMethod
if (!method || method === 'Always') text += '; runs every cycle'
else if (method === 'FileVersion') text += `; reinstalls unless version is ${entry.DetectionValue || 'set'}`
else if (method === 'Hash') text += '; re-copies if the file changed'
else if (method === 'MarkerFile') text += '; installs once'
else text += '; reinstalls if not detected'
if (entry._CmmVersion) text += `; CMM ${entry._CmmVersion} bays only`
else if (entry.TargetMachineNumbers && entry.TargetMachineNumbers.length) {
text += `; ${entry.TargetMachineNumbers.length} specific bay(s)`
} else if (entry.TargetHostnames && entry.TargetHostnames.length) {
text += '; specific hostname(s)'
} else if (entry.PCTypes && entry.PCTypes.length) {
text += `; ${entry.PCTypes.length} PC type(s)`
}
return text
}
function formatDate(value) {
return value ? new Date(value).toLocaleString() : ''
}
@@ -844,6 +892,13 @@ loadConfig()
/* 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-desc { font-size: 0.78rem; margin-top: 0.15rem; }
.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; }
.help-body { margin-top: 0.5rem; }
.help-body p { margin: 0 0 0.5rem 0; font-size: 0.85rem; color: var(--text-light); }
.help-body code { font-size: 0.8rem; }
.modal-close {
background: transparent;
border: none;