mirror of
https://github.com/vatesfr/xen-orchestra.git
synced 2026-11-09 17:06:13 -06:00
feat(xo6/user): show the username, first name and last name of users (#10657)
The users are now shown by their username, which replaces the deprecated name property, or by their email address when they have none. The users list and the user side panel also show their first and last names.
This commit is contained in:
committed by
GitHub
parent
35be9f2cc6
commit
25b837ef1a
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<UiTableCell>
|
||||
<div class="user-name">
|
||||
<div class="username">
|
||||
<UiUserLogo size="small" />
|
||||
<UiLink :to :href size="medium">
|
||||
{{ content }}
|
||||
@@ -23,7 +23,7 @@ defineProps<
|
||||
</script>
|
||||
|
||||
<style scoped lang="postcss">
|
||||
.user-name {
|
||||
.username {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.8rem;
|
||||
@@ -1,11 +1,11 @@
|
||||
import VtsUserNameCell from '@core/components/table/cells/VtsUserNameCell.vue'
|
||||
import VtsUsernameCell from '@core/components/table/cells/VtsUsernameCell.vue'
|
||||
import type { LinkOptions } from '@core/composables/link-component.composable.ts'
|
||||
import { defineColumn } from '@core/packages/table/define-column.ts'
|
||||
import { renderHeadCell } from '@core/tables/helpers/render-head-cell.ts'
|
||||
import type { HeaderConfig } from '@core/tables/types.ts'
|
||||
import { h } from 'vue'
|
||||
|
||||
export const useUserNameColumn = defineColumn((config?: HeaderConfig) => ({
|
||||
export const useUsernameColumn = defineColumn((config?: HeaderConfig) => ({
|
||||
renderHead: () => renderHeadCell(config?.headerLabel),
|
||||
renderBody: (content: string, link?: Pick<LinkOptions, 'to' | 'href'>) => h(VtsUserNameCell, { content, ...link }),
|
||||
renderBody: (content: string, link?: Pick<LinkOptions, 'to' | 'href'>) => h(VtsUsernameCell, { content, ...link }),
|
||||
}))
|
||||
@@ -3,7 +3,7 @@ import { useActionColumn } from '@core/tables/column-definitions/action-column.t
|
||||
import { useCheckboxColumn } from '@core/tables/column-definitions/checkbox-column.ts'
|
||||
import { useTextColumn } from '@core/tables/column-definitions/text-column.ts'
|
||||
import { useTruncatedTextColumn } from '@core/tables/column-definitions/truncated-text-column.ts'
|
||||
import { useUserNameColumn } from '@core/tables/column-definitions/user-name-column.ts'
|
||||
import { useUsernameColumn } from '@core/tables/column-definitions/username-column.ts'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
export const useUserColumns = defineColumns(() => {
|
||||
@@ -11,7 +11,7 @@ export const useUserColumns = defineColumns(() => {
|
||||
|
||||
return {
|
||||
checkbox: useCheckboxColumn(),
|
||||
username: useUserNameColumn({ headerLabel: () => t('username') }),
|
||||
username: useUsernameColumn({ headerLabel: () => t('username') }),
|
||||
firstName: useTextColumn({ headerLabel: () => t('first-name') }),
|
||||
lastName: useTextColumn({ headerLabel: () => t('last-name') }),
|
||||
email: useTruncatedTextColumn({ headerLabel: () => t('email') }),
|
||||
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
useUserQueryBuilder,
|
||||
} from '@/modules/user/composables/use-user-query-builder.composable.ts'
|
||||
import type { FrontXoUser } from '@/modules/user/remote-resources/use-xo-user-collection.ts'
|
||||
import { getUserDisplayName } from '@/modules/user/utils/xo-user.util.ts'
|
||||
import AssignmentsTableLayout from '@/shared/components/AssignmentsTableLayout.vue'
|
||||
import VtsQueryBuilder from '@core/components/query-builder/VtsQueryBuilder.vue'
|
||||
import VtsRow from '@core/components/table/VtsRow.vue'
|
||||
@@ -112,7 +113,7 @@ const { HeadCells, BodyCells } = useUserColumns({
|
||||
|
||||
return {
|
||||
checkbox: r => r({ selected: isSelected(user.id), onToggle: () => toggleSelection(user.id) }),
|
||||
username: r => r(user.name, { to: { name: '/admin/user/[id]/general', params: { id: user.id } } }),
|
||||
username: r => r(getUserDisplayName(user), { to: { name: '/admin/user/[id]/general', params: { id: user.id } } }),
|
||||
firstName: r => r(user.firstname),
|
||||
lastName: r => r(user.lastname),
|
||||
email: r => r(user.email),
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
size="small"
|
||||
:to="{ name: '/admin/user-management/users', query: { id: user.id } }"
|
||||
>
|
||||
{{ user.name ?? user.email }}
|
||||
{{ getUserDisplayName(user) }}
|
||||
</UiLink>
|
||||
</UiLinkList>
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
<script lang="ts" setup>
|
||||
import type { FrontXoGroup } from '@/modules/group/remote-resources/use-xo-group-collection.ts'
|
||||
import { useXoUserCollection } from '@/modules/user/remote-resources/use-xo-user-collection.ts'
|
||||
import { getUserDisplayName } from '@/modules/user/utils/xo-user.util.ts'
|
||||
import VtsStateHero from '@core/components/state-hero/VtsStateHero.vue'
|
||||
import UiLink from '@core/components/ui/link/UiLink.vue'
|
||||
import UiLinkList from '@core/components/ui/link-list/UiLinkList.vue'
|
||||
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
type FilterableUser,
|
||||
useUserQueryBuilder,
|
||||
} from '@/modules/user/composables/use-user-query-builder.composable.ts'
|
||||
import { getUserDisplayName } from '@/modules/user/utils/xo-user.util.ts'
|
||||
import AssignmentsTableLayout from '@/shared/components/AssignmentsTableLayout.vue'
|
||||
import VtsQueryBuilder from '@core/components/query-builder/VtsQueryBuilder.vue'
|
||||
import VtsRow from '@core/components/table/VtsRow.vue'
|
||||
@@ -115,7 +116,7 @@ const { HeadCells, BodyCells } = useUserColumns({
|
||||
return {
|
||||
checkbox: r =>
|
||||
user.isDirect ? r({ selected: isSelected(user.id), onToggle: () => toggleSelection(user.id) }) : r(),
|
||||
username: r => r(user.name, { to: { name: '/admin/user/[id]/general', params: { id: user.id } } }),
|
||||
username: r => r(getUserDisplayName(user), { to: { name: '/admin/user/[id]/general', params: { id: user.id } } }),
|
||||
firstName: r => r(user.firstname),
|
||||
lastName: r => r(user.lastname),
|
||||
email: r => r(user.email),
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
size="small"
|
||||
:to="{ name: '/admin/user-management/users', query: { id: user.id } }"
|
||||
>
|
||||
{{ user.name ?? user.email }}
|
||||
{{ getUserDisplayName(user) }}
|
||||
</UiLink>
|
||||
</UiLinkList>
|
||||
</div>
|
||||
@@ -36,6 +36,7 @@ import { useXoGroupCollection } from '@/modules/group/remote-resources/use-xo-gr
|
||||
import type { FrontXoRole } from '@/modules/role/remote-resources/use-xo-role-collection.ts'
|
||||
import { getRoleUserIds } from '@/modules/role/utils/xo-role.util.ts'
|
||||
import { type FrontXoUser, useXoUserCollection } from '@/modules/user/remote-resources/use-xo-user-collection.ts'
|
||||
import { getUserDisplayName } from '@/modules/user/utils/xo-user.util.ts'
|
||||
import VtsStateHero from '@core/components/state-hero/VtsStateHero.vue'
|
||||
import UiLink from '@core/components/ui/link/UiLink.vue'
|
||||
import UiLinkList from '@core/components/ui/link-list/UiLinkList.vue'
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
{{ t('user-details') }}
|
||||
</UiTitle>
|
||||
<VtsTabularKeyValueList>
|
||||
<VtsTabularKeyValueRow :label="t('username')" :value="user.name" />
|
||||
<VtsTabularKeyValueRow :label="t('username')" :value="user.username" />
|
||||
<VtsTabularKeyValueRow :label="t('uuid')" :value="user.id" />
|
||||
<VtsTabularKeyValueRow :label="t('last-login')" :value="lastLogin" />
|
||||
<VtsTabularKeyValueRow :label="t('first-name')" :value="user.firstname" />
|
||||
|
||||
@@ -10,12 +10,12 @@
|
||||
</UiLink>
|
||||
<span class="name">
|
||||
<UiUserLogo size="extra-small" />
|
||||
{{ user.name }}
|
||||
{{ getUserDisplayName(user) }}
|
||||
</span>
|
||||
</UiBreadcrumb>
|
||||
</div>
|
||||
<UiHeadBar>
|
||||
{{ user.name }}
|
||||
{{ getUserDisplayName(user) }}
|
||||
<template #icon>
|
||||
<UiUserLogo size="small" />
|
||||
</template>
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { FrontXoUser } from '@/modules/user/remote-resources/use-xo-user-collection.ts'
|
||||
import { getUserDisplayName } from '@/modules/user/utils/xo-user.util.ts'
|
||||
import VtsIcon from '@core/components/icon/VtsIcon.vue'
|
||||
import UiBreadcrumb from '@core/components/ui/breadcrumb/UiBreadcrumb.vue'
|
||||
import UiHeadBar from '@core/components/ui/head-bar/UiHeadBar.vue'
|
||||
|
||||
@@ -40,6 +40,7 @@ import {
|
||||
useUserQueryBuilder,
|
||||
} from '@/modules/user/composables/use-user-query-builder.composable.ts'
|
||||
import type { FrontXoUser } from '@/modules/user/remote-resources/use-xo-user-collection.ts'
|
||||
import { getUserDisplayName } from '@/modules/user/utils/xo-user.util.ts'
|
||||
import VtsOverlayCancelButton from '@core/components/overlay/VtsOverlayCancelButton.vue'
|
||||
import VtsOverlayConfirmButton from '@core/components/overlay/VtsOverlayConfirmButton.vue'
|
||||
import VtsQueryBuilder from '@core/components/query-builder/VtsQueryBuilder.vue'
|
||||
@@ -114,7 +115,7 @@ const { HeadCells, BodyCells } = useUserColumns({
|
||||
}),
|
||||
body: (user: FilterableUser) => ({
|
||||
checkbox: r => r({ selected: isSelected(user.id), onToggle: () => toggleSelection(user.id) }),
|
||||
username: r => r(user.name),
|
||||
username: r => r(getUserDisplayName(user)),
|
||||
firstName: r => r(user.firstname),
|
||||
lastName: r => r(user.lastname),
|
||||
email: r => r(user.email),
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<script setup lang="ts">
|
||||
import { useUserDelete } from '@/modules/user/composables/use-user-delete.composable.ts'
|
||||
import type { FrontXoUser } from '@/modules/user/remote-resources/use-xo-user-collection.ts'
|
||||
import { getUserProvidersLabel } from '@/modules/user/utils/xo-user.util.ts'
|
||||
import { getUserDisplayName, getUserProvidersLabel } from '@/modules/user/utils/xo-user.util.ts'
|
||||
import type { ActionItem } from '@core/components/menu/VtsActionsMenu.vue'
|
||||
import VtsTableBulkActions from '@core/components/table/VtsTableBulkActions.vue'
|
||||
import UiQuerySearchBar from '@core/components/ui/query-search-bar/UiQuerySearchBar.vue'
|
||||
@@ -100,7 +100,6 @@ const state = useTableState({
|
||||
})
|
||||
|
||||
const { HeadCells, BodyCells } = useUserColumns({
|
||||
exclude: ['firstName', 'lastName'],
|
||||
head: () => ({
|
||||
checkbox: r => r(pageSelectionModel),
|
||||
}),
|
||||
@@ -109,7 +108,9 @@ const { HeadCells, BodyCells } = useUserColumns({
|
||||
|
||||
return {
|
||||
checkbox: r => r({ selected: isSelected(user.id), onToggle: () => toggleSelection(user.id) }),
|
||||
username: r => r(user.name ?? '', { to: { name: '/admin/user/[id]/general', params: { id: user.id } } }),
|
||||
username: r => r(getUserDisplayName(user), { to: { name: '/admin/user/[id]/general', params: { id: user.id } } }),
|
||||
firstName: r => r(user.firstname ?? ''),
|
||||
lastName: r => r(user.lastname ?? ''),
|
||||
email: r => r(user.email),
|
||||
providers: r => r(getUserProvidersLabel(user, t('local'))),
|
||||
actions: r =>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<span class="name">
|
||||
<UiUserLogo size="small" />
|
||||
<UiLink :to="{ name: '/admin/user/[id]/general', params: { id: user.id } }" size="medium">
|
||||
{{ user.name }}
|
||||
{{ getUserDisplayName(user) }}
|
||||
</UiLink>
|
||||
</span>
|
||||
</template>
|
||||
@@ -25,6 +25,20 @@
|
||||
<VtsCopyButton :value="lastLogin" />
|
||||
</template>
|
||||
</VtsCardRowKeyValue>
|
||||
<VtsCardRowKeyValue truncate align-top>
|
||||
<template #key>{{ t('first-name') }}</template>
|
||||
<template #value>{{ user.firstname }}</template>
|
||||
<template v-if="user.firstname" #addons>
|
||||
<VtsCopyButton :value="user.firstname" />
|
||||
</template>
|
||||
</VtsCardRowKeyValue>
|
||||
<VtsCardRowKeyValue truncate align-top>
|
||||
<template #key>{{ t('last-name') }}</template>
|
||||
<template #value>{{ user.lastname }}</template>
|
||||
<template v-if="user.lastname" #addons>
|
||||
<VtsCopyButton :value="user.lastname" />
|
||||
</template>
|
||||
</VtsCardRowKeyValue>
|
||||
<VtsCardRowKeyValue truncate align-top>
|
||||
<template #key>{{ t('email') }}</template>
|
||||
<template #value>{{ user.email }}</template>
|
||||
@@ -43,6 +57,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { useXoUserUtils } from '@/modules/user/composables/use-xo-user-utils.composable.ts'
|
||||
import type { FrontXoUser } from '@/modules/user/remote-resources/use-xo-user-collection.ts'
|
||||
import { getUserDisplayName } from '@/modules/user/utils/xo-user.util.ts'
|
||||
import VtsCardRowKeyValue from '@core/components/card/VtsCardRowKeyValue.vue'
|
||||
import VtsCopyButton from '@core/components/copy-button/VtsCopyButton.vue'
|
||||
import UiLink from '@core/components/ui/link/UiLink.vue'
|
||||
|
||||
@@ -7,7 +7,7 @@ import { computed, type MaybeRefOrGetter, toValue } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
export type FilterableUser<TUser extends FrontXoUser = FrontXoUser> = TUser & {
|
||||
name: string
|
||||
username: string
|
||||
firstname: string
|
||||
lastname: string
|
||||
providers: string
|
||||
@@ -23,7 +23,7 @@ export function useUserQueryBuilder<TUser extends FrontXoUser>(
|
||||
const filterableUsers = computed<FilterableUser<TUser>[]>(() =>
|
||||
toValue(users).map(user => ({
|
||||
...user,
|
||||
name: user.name ?? '',
|
||||
username: user.username ?? '',
|
||||
firstname: user.firstname ?? '',
|
||||
lastname: user.lastname ?? '',
|
||||
providers: getUserProvidersLabel(user, t('local')),
|
||||
@@ -34,7 +34,7 @@ export function useUserQueryBuilder<TUser extends FrontXoUser>(
|
||||
|
||||
const schema = useQueryBuilderSchema<FilterableUser>({
|
||||
'': useStringSchema(t('any-property')),
|
||||
name: useStringSchema(t('username')),
|
||||
username: useStringSchema(t('username')),
|
||||
firstname: useStringSchema(t('first-name')),
|
||||
lastname: useStringSchema(t('last-name')),
|
||||
email: useStringSchema(t('email')),
|
||||
|
||||
@@ -13,6 +13,7 @@ const userFields = [
|
||||
'email',
|
||||
'firstname',
|
||||
'lastname',
|
||||
'username',
|
||||
'groups',
|
||||
'authProviders',
|
||||
] as const satisfies readonly (keyof XoUser)[]
|
||||
|
||||
@@ -6,6 +6,10 @@ export function getUserProvidersLabel(user: Pick<XoUser, 'authProviders'>, local
|
||||
return providerNames.length === 0 ? localLabel : providerNames.join(', ')
|
||||
}
|
||||
|
||||
export function getUserDisplayName(user: Pick<XoUser, 'username' | 'email'>) {
|
||||
return user.username || user.email
|
||||
}
|
||||
|
||||
export function getUserLastLoginTimestamp(tokens: XoAuthenticationToken[]) {
|
||||
let mostRecentTimestamp: number | undefined
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
- [XO6/Roles] List the privileges of a role in its detail page, and see the objects each privilege currently applies to in a side panel
|
||||
- [XO6/Privileges] Open a privilege's detail page from the privileges table of a role or its side panel, to see its details
|
||||
- [XO6/Privileges] See the scope of a privilege in its detail page: its scope type, its query and the objects it currently applies to
|
||||
- [XO6/Users] Show users by their username, or their email address when they have none, and show their first and last names in the users list and side panel
|
||||
|
||||
### Bug fixes
|
||||
|
||||
|
||||
Reference in New Issue
Block a user