Show per-PC credentials across the admin interface
- Endpoints (shared key) page: the key is presented as what PCs use until they enroll, with enrollment progress, a link to Enrollment, rewritten guidance, and rotate/revoke confirmations that say how many PCs are not enrolled yet. - Settings cards describe the server certificate as what PCs actually receive, on IIS or Docker; Enrollment has its own icon (the icon set lacked the one the Server Certificate card already asked for). - Server Certificate page checks the certificate the site presents at its public address and compares its pin with the certificate file, warning when they differ. The host comes from site settings only; the check is a TLS handshake, never an HTTP request. - Fleet list: Credential column and filter (enrolled, pending, re-enroll blocked, shared key, not reporting), computed in the list query. - PC detail: credential state, prefix and dates for fleet readers, state only for others, with admin Revoke (optionally blocking re-enrollment) and Allow re-enrollment. - Dashboard: pending enrollment requests, and PCs blocked from re-enrolling for more than seven days. - Runbook: where to find each of these in the app.
This commit is contained in:
@@ -41,6 +41,12 @@
|
||||
<option value="quiet">Quiet</option>
|
||||
<option value="never">Never reported</option>
|
||||
</select>
|
||||
<select v-model="credentialstate" class="form-control fleet-filter" @change="applyFilters">
|
||||
<option value="">All credentials</option>
|
||||
<option v-for="option in credentialOptions" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</option>
|
||||
</select>
|
||||
<select v-model="computertypeid" class="form-control fleet-filter" @change="applyFilters">
|
||||
<option value="">All PC types</option>
|
||||
<option v-for="type in computertypes" :key="type.computertypeid" :value="type.computertypeid">
|
||||
@@ -103,6 +109,7 @@
|
||||
<th>PC Type</th>
|
||||
<th>Health</th>
|
||||
<th>Agent Version</th>
|
||||
<th>Credential</th>
|
||||
<th>Logged-in User</th>
|
||||
<th>Uptime</th>
|
||||
<th>Last Reported</th>
|
||||
@@ -137,12 +144,17 @@
|
||||
<span v-else>-</span>
|
||||
</td>
|
||||
<td>{{ item.agentversion || '-' }}</td>
|
||||
<td>
|
||||
<span v-if="item.credentialstate" class="credential-chip"
|
||||
:class="item.credentialstate">{{ credentialLabel(item.credentialstate) }}</span>
|
||||
<span v-else>-</span>
|
||||
</td>
|
||||
<td>{{ item.loggedinuser || '-' }}</td>
|
||||
<td>{{ item.uptimedisplay || '-' }}</td>
|
||||
<td>{{ formatRelative(item.lastreportedat) }}</td>
|
||||
</tr>
|
||||
<tr v-if="endpoints.length === 0">
|
||||
<td :colspan="cancommand ? 11 : 10" style="text-align: center; color: var(--text-light);">
|
||||
<td :colspan="cancommand ? 12 : 11" style="text-align: center; color: var(--text-light);">
|
||||
No endpoints found
|
||||
</td>
|
||||
</tr>
|
||||
@@ -192,6 +204,17 @@ const sort = ref('status')
|
||||
const summary = ref(null)
|
||||
const criticalcount = ref(0)
|
||||
const computertypeid = ref('')
|
||||
// Which credential the PC reports with, computed server-side so the filter
|
||||
// holds across pages. Read-only here: revoke and re-enrollment live on the PC
|
||||
// detail page and the Enrollment settings page.
|
||||
const credentialstate = ref('')
|
||||
const credentialOptions = [
|
||||
{ value: 'enrolled', label: 'Enrolled' },
|
||||
{ value: 'pending', label: 'Pending' },
|
||||
{ value: 'reenrollblocked', label: 'Re-enroll blocked' },
|
||||
{ value: 'sharedkey', label: 'Shared key (not enrolled)' },
|
||||
{ value: 'notreporting', label: 'Not reporting' }
|
||||
]
|
||||
const locationid = ref('')
|
||||
const problemsonly = ref(false)
|
||||
const computertypes = ref([])
|
||||
@@ -329,6 +352,7 @@ async function loadEndpoints() {
|
||||
if (search.value) params.search = search.value
|
||||
if (status.value) params.status = status.value
|
||||
if (sort.value) params.sort = sort.value
|
||||
if (credentialstate.value) params.credentialstate = credentialstate.value
|
||||
if (computertypeid.value) params.computertypeid = computertypeid.value
|
||||
if (locationid.value) params.locationid = locationid.value
|
||||
|
||||
@@ -368,6 +392,11 @@ function openPc(item) {
|
||||
}
|
||||
}
|
||||
|
||||
function credentialLabel(value) {
|
||||
const option = credentialOptions.find(entry => entry.value === value)
|
||||
return option ? option.label : value
|
||||
}
|
||||
|
||||
function statusLabel(value) {
|
||||
return { reporting: 'Reporting', quiet: 'Quiet', never: 'Never' }[value] || '-'
|
||||
}
|
||||
@@ -394,6 +423,7 @@ function setStatus(value) {
|
||||
|
||||
function clearFilters() {
|
||||
status.value = ''
|
||||
credentialstate.value = ''
|
||||
problemsonly.value = false
|
||||
applyFilters()
|
||||
}
|
||||
@@ -515,6 +545,22 @@ function changePerPage(newPerPage) {
|
||||
|
||||
.fleet-filter { max-width: 200px; }
|
||||
|
||||
.credential-chip {
|
||||
display: inline-block;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 500;
|
||||
padding: 0.1rem 0.5rem;
|
||||
border-radius: 10px;
|
||||
background: var(--bg);
|
||||
color: var(--text-light);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.credential-chip.enrolled { color: var(--success-dark); }
|
||||
.credential-chip.pending { color: var(--warning-dark); }
|
||||
.credential-chip.reenrollblocked { color: var(--danger); }
|
||||
[data-theme="dark"] .credential-chip.enrolled { color: var(--success); }
|
||||
[data-theme="dark"] .credential-chip.pending { color: var(--warning); }
|
||||
|
||||
.bulk-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
Reference in New Issue
Block a user