134 lines
4.8 KiB
Vue
134 lines
4.8 KiB
Vue
<template>
|
|
<div class="overflow-x-auto rounded border border-kestrel-border">
|
|
<table class="w-full text-left text-sm">
|
|
<thead>
|
|
<tr class="border-b border-kestrel-border bg-kestrel-surface-hover">
|
|
<th class="px-4 py-2 font-medium text-kestrel-text">
|
|
Identifier
|
|
</th>
|
|
<th class="px-4 py-2 font-medium text-kestrel-text">
|
|
Auth
|
|
</th>
|
|
<th class="px-4 py-2 font-medium text-kestrel-text">
|
|
Role
|
|
</th>
|
|
<th
|
|
v-if="isAdmin"
|
|
class="px-4 py-2 font-medium text-kestrel-text"
|
|
>
|
|
Actions
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr
|
|
v-for="u in users"
|
|
:key="u.id"
|
|
class="border-b border-kestrel-border"
|
|
>
|
|
<td class="px-4 py-2 text-kestrel-text">
|
|
{{ u.identifier }}
|
|
</td>
|
|
<td class="px-4 py-2">
|
|
<span
|
|
class="rounded px-1.5 py-0.5 text-xs text-kestrel-muted"
|
|
:class="u.auth_provider === 'oidc' ? 'bg-kestrel-surface' : ''"
|
|
>
|
|
{{ u.auth_provider === 'oidc' ? 'OIDC' : 'Local' }}
|
|
</span>
|
|
</td>
|
|
<td class="px-4 py-2">
|
|
<AppDropdown
|
|
v-if="isAdmin"
|
|
:open="openRoleDropdownId === u.id"
|
|
teleport
|
|
@close="emit('closeRoleDropdown')"
|
|
>
|
|
<button
|
|
type="button"
|
|
class="flex min-w-[6rem] items-center justify-between gap-2 rounded border border-kestrel-border bg-kestrel-bg px-2 py-1 text-left text-sm text-kestrel-text shadow-sm transition-colors hover:border-kestrel-accent/50 hover:bg-kestrel-surface"
|
|
:aria-expanded="openRoleDropdownId === u.id"
|
|
:aria-haspopup="true"
|
|
aria-label="Change role"
|
|
@click.stop="emit('toggleRoleDropdown', u.id)"
|
|
>
|
|
<span>{{ roleByUserId[u.id] ?? u.role }}</span>
|
|
<span
|
|
class="text-kestrel-muted transition-transform"
|
|
:class="openRoleDropdownId === u.id && 'rotate-180'"
|
|
>
|
|
▾
|
|
</span>
|
|
</button>
|
|
<template #menu>
|
|
<button
|
|
v-for="role in roleOptions"
|
|
:key="role"
|
|
type="button"
|
|
role="menuitem"
|
|
class="block w-full px-3 py-1.5 text-left text-sm transition-colors"
|
|
:class="roleByUserId[u.id] === role
|
|
? 'bg-kestrel-accent-dim text-kestrel-accent'
|
|
: 'text-kestrel-text hover:bg-kestrel-border hover:text-kestrel-text'"
|
|
@click.stop="emit('selectRole', u.id, role)"
|
|
>
|
|
{{ role }}
|
|
</button>
|
|
</template>
|
|
</AppDropdown>
|
|
<span
|
|
v-else
|
|
class="text-kestrel-muted"
|
|
>{{ u.role }}</span>
|
|
</td>
|
|
<td
|
|
v-if="isAdmin"
|
|
class="px-4 py-2"
|
|
>
|
|
<div class="flex flex-wrap items-center gap-2">
|
|
<button
|
|
v-if="roleByUserId[u.id] !== u.role"
|
|
type="button"
|
|
class="rounded border border-kestrel-accent px-2 py-1 text-xs text-kestrel-accent hover:bg-kestrel-accent-dim"
|
|
@click="emit('saveRole', u.id)"
|
|
>
|
|
Save role
|
|
</button>
|
|
<template v-if="u.auth_provider !== 'oidc'">
|
|
<button
|
|
type="button"
|
|
class="rounded border border-kestrel-border px-2 py-1 text-xs text-kestrel-text hover:bg-kestrel-surface"
|
|
@click="emit('editUser', u)"
|
|
>
|
|
Edit
|
|
</button>
|
|
<button
|
|
v-if="u.id !== currentUserId"
|
|
type="button"
|
|
class="rounded border border-red-500/60 px-2 py-1 text-xs text-red-400 hover:bg-red-500/10"
|
|
@click="emit('deleteConfirm', u)"
|
|
>
|
|
Remove
|
|
</button>
|
|
</template>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
defineProps({
|
|
users: { type: Array, required: true },
|
|
roleByUserId: { type: Object, required: true },
|
|
roleOptions: { type: Array, required: true },
|
|
isAdmin: Boolean,
|
|
currentUserId: { type: [String, Number], default: null },
|
|
openRoleDropdownId: { type: [String, Number], default: null },
|
|
})
|
|
|
|
const emit = defineEmits(['toggleRoleDropdown', 'closeRoleDropdown', 'selectRole', 'saveRole', 'editUser', 'deleteConfirm'])
|
|
</script>
|