guest role; guide section ticking
This commit is contained in:
@@ -11,7 +11,9 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-search">
|
||||
<!-- Global search is authenticated; offering the box to a signed-out
|
||||
reader gives them a control that can only answer 401. -->
|
||||
<div v-if="authStore.isAuthenticated" class="sidebar-search">
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
type="text"
|
||||
@@ -222,8 +224,23 @@ function buildNavItems(items) {
|
||||
// Drop items the user cannot reach: a nav item may carry a `permission`
|
||||
// (e.g. Slides -> slides.manage); hide it from anyone who lacks it so the
|
||||
// link does not dead-end at the router guard. Admins hold every permission.
|
||||
const visible = items.filter(item =>
|
||||
!item.permission || authStore.hasPermission(item.permission))
|
||||
//
|
||||
// A signed-out visitor sees only what a plugin has declared guest-reachable.
|
||||
// Without this the sidebar offers a signed-out reader the whole app and every
|
||||
// link bounces them to the login page, which reads as a broken site rather
|
||||
// than as a closed door.
|
||||
// A nav item may name one permission or several; holding ANY of them is
|
||||
// enough. GE-Enforce and Slides need that: the page is reachable by a
|
||||
// manager and by a plain viewer, and they hold different strings.
|
||||
const holds = item => {
|
||||
if (!item.permission) return true
|
||||
const names = Array.isArray(item.permission) ? item.permission : [item.permission]
|
||||
return names.some(name => authStore.hasPermission(name))
|
||||
}
|
||||
// A signed-out visitor gets whatever the server sent, which is already
|
||||
// filtered to what the guest role can reach - hasPermission knows nothing
|
||||
// about guests, so asking it here would hide everything.
|
||||
const visible = authStore.isAuthenticated ? items.filter(holds) : items
|
||||
|
||||
const known = new Set(NAV_SECTIONS.map(s => s.key).filter(Boolean))
|
||||
const bucket = item => {
|
||||
|
||||
@@ -225,6 +225,17 @@
|
||||
<p class="text-muted" style="font-size: 0.85rem; margin: 0 0 0.5rem 0">
|
||||
Select which actions this role can perform
|
||||
</p>
|
||||
<!-- The guest role is everyone who is not signed in, so what it may
|
||||
hold is bounded: reads, plus the few writes a plugin has
|
||||
declared safe to do anonymously. The rest are disabled here
|
||||
rather than refused on save, so the limit is visible while the
|
||||
decision is being made. -->
|
||||
<p v-if="editingRole?.isguest" class="guest-note">
|
||||
This role is <strong>everyone who is not signed in</strong>.
|
||||
Anything ticked here is readable by anybody who can reach this
|
||||
site, with no account and no name in the audit log. Greyed-out
|
||||
permissions cannot be given to guests at all.
|
||||
</p>
|
||||
<div class="permissions-grid">
|
||||
<div v-for="(perms, category) in permissionsGrouped" :key="category" class="permission-category">
|
||||
<div class="category-header">
|
||||
@@ -239,10 +250,14 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="category-perms">
|
||||
<label v-for="p in perms" :key="p.name" class="checkbox-label perm-item">
|
||||
<label v-for="p in perms" :key="p.name" class="checkbox-label perm-item"
|
||||
:class="{ 'perm-blocked': !mayGrant(p.name) }"
|
||||
:title="mayGrant(p.name) ? null
|
||||
: 'A guest cannot be given this. Reading is grantable; a write has to be declared safe to do without an account.'">
|
||||
<input
|
||||
type="checkbox"
|
||||
:value="p.name"
|
||||
:disabled="!mayGrant(p.name)"
|
||||
v-model="roleForm.permissions"
|
||||
>
|
||||
{{ p.description }}
|
||||
@@ -382,19 +397,33 @@ function formatCategory(cat) {
|
||||
return cat.charAt(0).toUpperCase() + cat.slice(1)
|
||||
}
|
||||
|
||||
// Which permissions this role is allowed to hold at all. Only the guest role
|
||||
// is bounded; the API sends the list it will accept, so the grid and the server
|
||||
// cannot disagree about it.
|
||||
function mayGrant(name) {
|
||||
if (!editingRole.value?.isguest) return true
|
||||
const grantable = editingRole.value?.grantable
|
||||
return Array.isArray(grantable) ? grantable.includes(name) : false
|
||||
}
|
||||
|
||||
function isCategoryFullySelected(category) {
|
||||
const catPerms = permissionsGrouped.value[category] || []
|
||||
const catPerms = (permissionsGrouped.value[category] || [])
|
||||
.filter(p => mayGrant(p.name))
|
||||
return catPerms.length > 0 && catPerms.every(p => roleForm.permissions.includes(p.name))
|
||||
}
|
||||
|
||||
function isCategoryPartiallySelected(category) {
|
||||
const catPerms = permissionsGrouped.value[category] || []
|
||||
const catPerms = (permissionsGrouped.value[category] || [])
|
||||
.filter(p => mayGrant(p.name))
|
||||
const selected = catPerms.filter(p => roleForm.permissions.includes(p.name))
|
||||
return selected.length > 0 && selected.length < catPerms.length
|
||||
}
|
||||
|
||||
function toggleCategory(category, checked) {
|
||||
const catPerms = permissionsGrouped.value[category] || []
|
||||
// Ticking a whole category must not try to grant a guest something the API
|
||||
// will refuse - the header checkbox would then look broken.
|
||||
const catPerms = (permissionsGrouped.value[category] || [])
|
||||
.filter(p => mayGrant(p.name))
|
||||
const permNames = catPerms.map(p => p.name)
|
||||
|
||||
if (checked) {
|
||||
@@ -780,4 +809,13 @@ onMounted(loadData)
|
||||
font-size: 0.85rem;
|
||||
padding: 0.25rem 0;
|
||||
}
|
||||
|
||||
.perm-blocked { opacity: 0.45; cursor: not-allowed; }
|
||||
.guest-note {
|
||||
font-size: 0.85rem;
|
||||
color: var(--warning);
|
||||
border-left: 3px solid var(--warning);
|
||||
padding-left: 0.6rem;
|
||||
margin: 0 0 0.75rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user