API cleanup, using msgspec structs rather than raw responses. Admin app cleanup, better breadcrumbs.
This commit is contained in:
1 parent
423abb0d1b
commit
c1f8020f6b
21 files changed
+505
-335
No files matched your search
@@ -16,28 +16,47 @@ const permMatrixRef = ref(null)
|
||||
const rolesGridRef = ref(null)
|
||||
|
||||
const sortedRoles = computed(() => {
|
||||
return [...props.selectedOrg.roles].sort((a, b) => {
|
||||
const nameA = a.display_name.toLowerCase()
|
||||
const nameB = b.display_name.toLowerCase()
|
||||
if (nameA !== nameB) {
|
||||
return nameA.localeCompare(nameB)
|
||||
}
|
||||
return a.uuid.localeCompare(b.uuid)
|
||||
})
|
||||
return Object.entries(props.selectedOrg.roles)
|
||||
.map(([uuid, r]) => ({ uuid, ...r }))
|
||||
.sort((a, b) => {
|
||||
const nameA = a.display_name.toLowerCase()
|
||||
const nameB = b.display_name.toLowerCase()
|
||||
if (nameA !== nameB) {
|
||||
return nameA.localeCompare(nameB)
|
||||
}
|
||||
return a.uuid.localeCompare(b.uuid)
|
||||
})
|
||||
})
|
||||
|
||||
// Get users for a role as sorted array of { uuid, ...user }
|
||||
function roleUsers(roleUuid) {
|
||||
return Object.entries(props.selectedOrg.users)
|
||||
.filter(([_, u]) => u.role === roleUuid)
|
||||
.map(([uuid, u]) => ({ uuid, ...u }))
|
||||
.sort((a, b) => {
|
||||
const nameA = a.display_name.toLowerCase()
|
||||
const nameB = b.display_name.toLowerCase()
|
||||
return nameA.localeCompare(nameB)
|
||||
})
|
||||
}
|
||||
|
||||
function roleUserCount(roleUuid) {
|
||||
return Object.values(props.selectedOrg.users).filter(u => u.role === roleUuid).length
|
||||
}
|
||||
|
||||
// Get org's grantable permissions as full permission objects (with UUIDs)
|
||||
const orgPermissions = computed(() => {
|
||||
const uuidSet = new Set(props.selectedOrg.permissions || [])
|
||||
return props.permissions.filter(p => uuidSet.has(p.uuid))
|
||||
return Object.entries(props.selectedOrg.permissions)
|
||||
.map(([uuid, p]) => ({ uuid, ...p }))
|
||||
.sort((a, b) => a.scope.localeCompare(b.scope))
|
||||
})
|
||||
|
||||
function permissionDisplayName(scope) {
|
||||
return props.permissions.find(p => p.scope === scope)?.display_name || scope
|
||||
}
|
||||
|
||||
function toggleRolePermission(role, pid, checked) {
|
||||
emit('toggleRolePermission', role, pid, checked)
|
||||
function toggleRolePermission(roleUuid, role, pid, checked) {
|
||||
emit('toggleRolePermission', roleUuid, role, pid, checked)
|
||||
}
|
||||
|
||||
// Handle org title header keynav
|
||||
@@ -287,7 +306,7 @@ defineExpose({ focusFirstElement })
|
||||
|
||||
<template>
|
||||
<h2 class="org-title" ref="orgTitleRef" @keydown="handleTitleKeydown" :title="selectedOrg.uuid">
|
||||
<span class="org-name">{{ selectedOrg.display_name }}</span>
|
||||
<span class="org-name">{{ selectedOrg.org.display_name }}</span>
|
||||
<button @click="$emit('updateOrg', selectedOrg)" class="icon-btn" aria-label="Rename organization" title="Rename organization">✏️</button>
|
||||
</h2>
|
||||
|
||||
@@ -317,8 +336,8 @@ defineExpose({ focusFirstElement })
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="r.permissions.includes(p.uuid)"
|
||||
@change="e => toggleRolePermission(r, p.uuid, e.target.checked)"
|
||||
:checked="p.uuid in (r.permissions || {})"
|
||||
@change="e => toggleRolePermission(r.uuid, r, p.uuid, e.target.checked)"
|
||||
/>
|
||||
</div>
|
||||
<div class="matrix-cell add-role-cell" />
|
||||
@@ -333,34 +352,27 @@ defineExpose({ focusFirstElement })
|
||||
:key="r.uuid"
|
||||
class="role-column"
|
||||
@dragover="$emit('onRoleDragOver', $event)"
|
||||
@drop="e => $emit('onRoleDrop', e, selectedOrg, r)"
|
||||
@drop="e => $emit('onRoleDrop', e, selectedOrg, r.uuid)"
|
||||
>
|
||||
<div class="role-header" @keydown="e => handleRoleHeaderKeydown(e, roleIndex)">
|
||||
<strong class="role-name" :title="r.uuid">
|
||||
<span>{{ r.display_name }}</span>
|
||||
<button @click="$emit('updateRole', r)" class="icon-btn" aria-label="Edit role" title="Edit role">✏️</button>
|
||||
<button v-if="r.users.length === 0" @click="$emit('deleteRole', r)" class="icon-btn delete-icon" aria-label="Delete role" title="Delete role">❌</button>
|
||||
<button v-if="roleUserCount(r.uuid) === 0" @click="$emit('deleteRole', r.uuid, r)" class="icon-btn delete-icon" aria-label="Delete role" title="Delete role">❌</button>
|
||||
</strong>
|
||||
<div class="role-actions">
|
||||
<button @click="$emit('createUserInRole', selectedOrg, r)" class="plus-btn" aria-label="Add user" title="Add user">➕</button>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="r.users.length > 0">
|
||||
<template v-if="roleUserCount(r.uuid) > 0">
|
||||
<ul class="user-list" @keydown="handleUserListKeydown">
|
||||
<li
|
||||
v-for="u in r.users.slice().sort((a, b) => {
|
||||
const nameA = a.display_name.toLowerCase()
|
||||
const nameB = b.display_name.toLowerCase()
|
||||
if (nameA !== nameB) {
|
||||
return nameA.localeCompare(nameB)
|
||||
}
|
||||
return a.uuid.localeCompare(b.uuid)
|
||||
})"
|
||||
v-for="u in roleUsers(r.uuid)"
|
||||
:key="u.uuid"
|
||||
class="user-chip"
|
||||
tabindex="0"
|
||||
draggable="true"
|
||||
@dragstart="e => $emit('onUserDragStart', e, u, selectedOrg.uuid)"
|
||||
@dragstart="e => $emit('onUserDragStart', e, u.uuid, selectedOrg.uuid)"
|
||||
@click="$emit('openUser', u)"
|
||||
@keydown.enter="$emit('openUser', u)"
|
||||
:title="u.uuid"
|
||||
|
||||
Reference in new issue
Block a user