Admin UI: runtime diagnostics as a 🔸 list, placed after the origins help text

This commit is contained in:
2026-09-07 16:15:39 +00:00
parent 8baa8eb590
commit d37ed9c7bb
2 changed files with 48 additions and 10 deletions
+23 -10
View File
@@ -95,6 +95,15 @@ const lockoutWarning = computed(() => {
return pageOriginAllowed(d.origins || [], rpId) ? null : window.location.host return pageOriginAllowed(d.origins || [], rpId) ? null : window.location.host
}) })
// Whether any origin diagnostic is present (guards the list against
// occupying dialog spacing when empty)
const hasOriginDiagnostics = computed(() => {
const d = props.dialog?.data
if (props.dialog?.type !== 'domain-edit' || !d) return false
if (d.originValidation?.some(v => v === 'invalid' || v === 'unreachable' || v === 'mismatch')) return true
return relatedEntries.value.length > 5 || !!lockoutWarning.value
})
function pageOriginAllowed(rows, rpId) { function pageOriginAllowed(rows, rpId) {
const toUrl = key => (isWildcardEntry(key) || key.includes('://')) ? key : 'https://' + key const toUrl = key => (isWildcardEntry(key) || key.includes('://')) ? key : 'https://' + key
const inDomain = [] const inDomain = []
@@ -511,17 +520,19 @@ function onRemoveOrigin(i) {
</div> </div>
</div> </div>
</div> </div>
<p v-if="dialog.data.originValidation.some(v => v === 'invalid')" class="small error">Some entries are invalid — a bare '*' or '**' is not allowed, and wildcards only within the domain.</p>
<p v-if="dialog.data.originValidation.some(v => v === 'unreachable')" class="small muted">Some sites are unreachable — make sure they are routed to this instance.</p>
<p v-else-if="dialog.data.originValidation.some(v => v === 'mismatch')" class="small muted">Some sites are reachable but do not serve this domain.</p>
</div> </div>
<p class="small muted"> <p class="small muted">
Only the listed sites may sign in with {{ dialog.data.rp_id }} passkeys. Wildcards may be used: <strong>**.{{ dialog.data.rp_id }}</strong> allows the whole domain, <strong>*.{{ dialog.data.rp_id }}</strong> only a single subdomain level.<template v-if="relatedEntries.length"> Entries on other domain names become related origins (WebAuthn ROR), marked 🔗, and cannot use any wildcards.</template><template v-if="dialog.data.auth_host"> The 🔑 site hosts the account and admin interface (set via ⋮).</template> Only the listed sites may sign in with {{ dialog.data.rp_id }} passkeys. Wildcards may be used: <strong>**.{{ dialog.data.rp_id }}</strong> allows the whole domain, <strong>*.{{ dialog.data.rp_id }}</strong> only a single subdomain level.<template v-if="relatedEntries.length"> 🔗 means related host requiring WebAuthn ROR setup.</template><template v-if="dialog.data.auth_host"> 🔑 is the dedicated Paskia host for all account management.</template>
</p> </p>
<p v-if="lockoutWarning" class="small error">Saving would lock you out: {{ lockoutWarning }} could no longer run sign-in ceremonies for this domain. Keep it listed, or mark an auth host.</p> <ul v-if="hasOriginDiagnostics" class="diag-list">
<li v-if="dialog.data.originValidation.some(v => v === 'invalid')" class="small error">Some entries are invalid — a bare '*' or '**' is not allowed, and wildcards only within the domain.</li>
<li v-if="dialog.data.originValidation.some(v => v === 'unreachable')" class="small muted">Some sites are unreachable — make sure they are routed to this instance.</li>
<li v-else-if="dialog.data.originValidation.some(v => v === 'mismatch')" class="small muted">Some sites are reachable but do not serve this domain.</li>
<li v-if="relatedEntries.length > 5" class="small error">At most 5 related origins are allowed ({{ relatedEntries.length }} listed) — the save is rejected.</li>
<li v-if="lockoutWarning" class="small error">Saving would lock you out: {{ lockoutWarning }} could no longer run sign-in ceremonies for this domain. Keep it listed, or mark an auth host.</li>
</ul>
<template v-if="relatedEntries.length"> <template v-if="relatedEntries.length">
<p v-if="relatedEntries.length > 5" class="small error">At most 5 related origins are allowed ({{ relatedEntries.length }} listed) — the save is rejected.</p>
<p class="small muted"> <p class="small muted">
Related origins are verified by browsers against Related origins are verified by browsers against
<a :href="wellKnownUrl" target="_blank" rel="noopener noreferrer">{{ wellKnownUrl }}</a> <a :href="wellKnownUrl" target="_blank" rel="noopener noreferrer">{{ wellKnownUrl }}</a>
@@ -531,10 +542,12 @@ function onRemoveOrigin(i) {
<pre>{{ wellKnownJson }}</pre> <pre>{{ wellKnownJson }}</pre>
<button type="button" class="icon-btn" @click="copyText(wellKnownJson, 'Well-known document')" aria-label="Copy well-known document" title="Copy">📋</button> <button type="button" class="icon-btn" @click="copyText(wellKnownJson, 'Well-known document')" aria-label="Copy well-known document" title="Copy">📋</button>
</div> </div>
<p v-if="dialog.data.wellKnownCheck === 'validating'" class="small muted">Checking the published document…</p> <ul v-if="dialog.data.wellKnownCheck" class="diag-list">
<p v-else-if="dialog.data.wellKnownCheck === 'valid'" class="small muted">✓ The published document lists all related origins.</p> <li v-if="dialog.data.wellKnownCheck === 'validating'" class="small muted">Checking the published document…</li>
<p v-else-if="dialog.data.wellKnownCheck === 'missing'" class="small error">The published document does not list: {{ (dialog.data.wellKnownMissing || []).join(', ') }}</p> <li v-else-if="dialog.data.wellKnownCheck === 'valid'" class="small muted">The published document lists all related origins.</li>
<p v-else-if="dialog.data.wellKnownCheck === 'unreachable'" class="small muted">Could not fetch the published document to verify it.</p> <li v-else-if="dialog.data.wellKnownCheck === 'missing'" class="small error">The published document does not list: {{ (dialog.data.wellKnownMissing || []).join(', ') }}</li>
<li v-else-if="dialog.data.wellKnownCheck === 'unreachable'" class="small muted">Could not fetch the published document to verify it.</li>
</ul>
</template> </template>
</template> </template>
<template v-else-if="dialog.type==='confirm'"> <template v-else-if="dialog.type==='confirm'">
+25
View File
@@ -467,6 +467,31 @@ th {
font-size: 0.9rem; font-size: 0.9rem;
} }
/* Runtime diagnostics list: 🔸 markers with a hanging indent, so wrapped
lines align with the text rather than under the marker */
.diag-list {
list-style: none;
margin: 0;
padding: 0;
}
.diag-list li {
position: relative;
padding-left: 1.4em;
}
.diag-list li + li {
margin-top: 0.3em;
}
.diag-list li::before {
content: "🔸";
position: absolute;
left: 0;
top: 0.12em;
font-size: 0.8em;
}
.icon-btn { .icon-btn {
background: none; background: none;
border: none; border: none;