Skip to content

Commit da09407

Browse files
committed
feat(xo6/group): add groups list in user management page (#10272)
1 parent 70b0824 commit da09407

14 files changed

Lines changed: 201 additions & 12 deletions

File tree

‎@xen-orchestra/web-core/lib/locales/en.json‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,8 @@
451451
"gpu": "GPU",
452452
"gpus": "GPUs",
453453
"graphics-display": "Graphics & Display",
454+
"group:what-is-a-group-description": "A group is a collection of users that share the same roles. Assign roles to a group to automatically grant those permissions to all its members. When a user is added to a group, they inherit all roles assigned to that group; inherited roles cannot be removed individually from a user, only by changing the group's roles or removing the user from the group.",
455+
"group:what-is-a-group?": "What is a group?",
454456
"groups": "Groups",
455457
"guest-tools": "Guest tools",
456458
"guest-tools-out-of-date": "Guest tools out of date",
@@ -878,6 +880,7 @@
878880
"no-data": "No data",
879881
"no-data-to-calculate": "No data to calculate",
880882
"no-group-attached": "No group attached",
883+
"no-group-detected": "No groups detected",
881884
"no-host-attached": "No hosts attached",
882885
"no-host-detected": "No hosts detected",
883886
"no-job-vm": "This VM isn’t part of any running job.",
@@ -1018,6 +1021,7 @@
10181021
"protection-status": "Protection status",
10191022
"protocol": "Protocol",
10201023
"protocol:port": "@:{'protocol'}:@:{'port'}",
1024+
"provider": "Provider",
10211025
"provider-solutions": "{provider} solutions",
10221026
"providers": "Providers",
10231027
"provisioning": "Provisioning",

‎@xen-orchestra/web-core/lib/locales/fr.json‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,8 @@
451451
"gpu": "GPU",
452452
"gpus": "GPUs",
453453
"graphics-display": "Graphique & Affichage",
454+
"group:what-is-a-group-description": "Un groupe est un ensemble d'utilisateurs qui partagent les mêmes rôles. Attribuez des rôles à un groupe pour accorder automatiquement ces permissions à tous ses membres. Lorsqu'un utilisateur est ajouté à un groupe, il hérite de tous les rôles attribués à ce groupe ; les rôles hérités ne peuvent pas être retirés individuellement d'un utilisateur, mais uniquement en modifiant les rôles du groupe ou en retirant l'utilisateur du groupe.",
455+
"group:what-is-a-group?": "Qu'est-ce qu'un groupe ?",
454456
"groups": "Groupes",
455457
"guest-tools": "Guest tools",
456458
"guest-tools-out-of-date": "Guest tools obsolètes",
@@ -878,6 +880,7 @@
878880
"no-data": "Aucune donnée",
879881
"no-data-to-calculate": "Aucune donnée à calculer",
880882
"no-group-attached": "Pas de groupe attaché",
883+
"no-group-detected": "Aucun groupe détecté",
881884
"no-host-attached": "Aucun hôte attaché",
882885
"no-host-detected": "Aucun hôte détecté",
883886
"no-job-vm": "Cette VM ne fait partie d'aucun job actif.",
@@ -1018,6 +1021,7 @@
10181021
"protection-status": "Statut de la protection",
10191022
"protocol": "Protocole",
10201023
"protocol:port": "@:{'protocol'}:@:{'port'}",
1024+
"provider": "Fournisseur",
10211025
"provider-solutions": "Solutions {provider}",
10221026
"providers": "Fournisseurs",
10231027
"provisioning": "Provisionnement",
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { defineColumns } from '@core/packages/table/define-columns.ts'
2+
import { useNumberColumn } from '@core/tables/column-definitions/number-column.ts'
3+
import { useTextColumn } from '@core/tables/column-definitions/text-column.ts'
4+
import { useI18n } from 'vue-i18n'
5+
6+
export const useGroupColumns = defineColumns(() => {
7+
const { t } = useI18n()
8+
9+
return {
10+
name: useTextColumn({ headerLabel: () => t('name') }),
11+
provider: useTextColumn({ headerLabel: () => t('provider') }),
12+
users: useNumberColumn({ headerLabel: () => t('users') }),
13+
roles: useNumberColumn({ headerLabel: () => t('roles') }),
14+
}
15+
})
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
declare module '@xen-orchestra/web-core/packages/hide-permanently/types.ts' {
2+
interface PermanentlyHideableItems {
3+
'what-is-a-group': true
4+
}
5+
}
6+
7+
export {}

‎@xen-orchestra/web/src/modules/admin/components/AdministrationMenu.vue‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ const sections = computed<AdminMenuSection[]>(() => [
5151
title: t('user-management'),
5252
items: [
5353
{ label: t('users'), href: undefined, to: { name: '/admin/user-management' } },
54-
{ label: t('groups'), href: buildXo5Route('/settings/groups'), to: undefined },
54+
{ label: t('groups'), href: undefined, to: { name: '/admin/user-management/groups' } },
5555
{ label: t('roles'), href: buildXo5Route('/settings/acls'), to: undefined },
5656
{
5757
label: t('ldap-auth-providers'),

‎@xen-orchestra/web/src/modules/admin/components/UserManagementHeader.vue‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88
{{ t('users') }}
99
</UiTabItem>
1010
</RouterLink>
11+
<RouterLink v-slot="{ isExactActive, href }" :to="{ name: '/admin/user-management/groups' }" custom>
12+
<UiTabItem :active="isExactActive" :href tag="a">
13+
{{ t('groups') }}
14+
</UiTabItem>
15+
</RouterLink>
1116
</TabList>
1217
</template>
1318

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<template>
2+
<div class="groups-table">
3+
<UiTitle>
4+
{{ t('groups') }}
5+
</UiTitle>
6+
<VtsQueryBuilder v-model="filter" :schema />
7+
<div class="container">
8+
<VtsTable :state :pagination-bindings>
9+
<thead>
10+
<tr>
11+
<HeadCells />
12+
</tr>
13+
</thead>
14+
<tbody>
15+
<VtsRow v-for="group of paginatedGroups" :key="group.id">
16+
<BodyCells :item="group" />
17+
</VtsRow>
18+
</tbody>
19+
</VtsTable>
20+
</div>
21+
</div>
22+
</template>
23+
24+
<script setup lang="ts">
25+
import type { FrontXoGroup } from '@/modules/group/remote-resources/use-xo-group-collection.ts'
26+
import VtsQueryBuilder from '@core/components/query-builder/VtsQueryBuilder.vue'
27+
import VtsRow from '@core/components/table/VtsRow.vue'
28+
import VtsTable from '@core/components/table/VtsTable.vue'
29+
import UiTitle from '@core/components/ui/title/UiTitle.vue'
30+
import { usePagination } from '@core/composables/pagination.composable.ts'
31+
import { useTableState } from '@core/composables/table-state.composable.ts'
32+
import { useQueryBuilderSchema } from '@core/packages/query-builder/schema/use-query-builder-schema.ts'
33+
import { useQueryBuilderFilter } from '@core/packages/query-builder/use-query-builder-filter.ts'
34+
import { useGroupColumns } from '@core/tables/column-sets/group-columns.ts'
35+
import { useNumberSchema } from '@core/utils/query-builder/use-number-schema.ts'
36+
import { useStringSchema } from '@core/utils/query-builder/use-string-schema.ts'
37+
import { computed } from 'vue'
38+
import { useI18n } from 'vue-i18n'
39+
40+
type FilterableGroup = FrontXoGroup & { provider: string; usersCount: number; rolesCount: number }
41+
42+
const {
43+
groups: rawGroups,
44+
busy,
45+
error,
46+
} = defineProps<{
47+
groups: FrontXoGroup[]
48+
busy?: boolean
49+
error?: boolean
50+
}>()
51+
52+
const { t } = useI18n()
53+
54+
const filterableGroups = computed<FilterableGroup[]>(() =>
55+
rawGroups.map(group => ({
56+
...group,
57+
provider: group.provider ?? '',
58+
usersCount: group.users.length,
59+
rolesCount: group.aclRoleIds.length,
60+
}))
61+
)
62+
63+
const { items: filteredGroups, filter } = useQueryBuilderFilter('groups', () => filterableGroups.value)
64+
65+
const schema = useQueryBuilderSchema<FilterableGroup>({
66+
'': useStringSchema(t('any-property')),
67+
name: useStringSchema(t('name')),
68+
provider: useStringSchema(t('provider')),
69+
usersCount: useNumberSchema(t('users')),
70+
rolesCount: useNumberSchema(t('roles')),
71+
})
72+
73+
const state = useTableState({
74+
busy: () => busy,
75+
error: () => error,
76+
empty: () =>
77+
rawGroups.length === 0 ? t('no-group-detected') : filteredGroups.value.length === 0 ? { type: 'no-result' } : false,
78+
})
79+
80+
const { pageRecords: paginatedGroups, paginationBindings } = usePagination('groups', filteredGroups)
81+
82+
const { HeadCells, BodyCells } = useGroupColumns({
83+
body: (group: FilterableGroup) => ({
84+
name: r => r(group.name),
85+
provider: r => r(group.provider),
86+
users: r => r(group.usersCount),
87+
roles: r => r(group.rolesCount),
88+
}),
89+
})
90+
</script>
91+
92+
<style scoped lang="postcss">
93+
.groups-table,
94+
.container {
95+
display: flex;
96+
flex-direction: column;
97+
gap: 2.4rem;
98+
}
99+
100+
.container {
101+
gap: 0.8rem;
102+
}
103+
</style>

@xen-orchestra/web/src/modules/group/remote-ressources/use-xo-group-collection.ts renamed to @xen-orchestra/web/src/modules/group/remote-resources/use-xo-group-collection.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type { XoGroup } from '@vates/types'
66

77
export type FrontXoGroup = Pick<XoGroup, (typeof groupFields)[number]>
88

9-
const groupFields = ['id', 'name'] as const satisfies readonly (keyof XoGroup)[]
9+
const groupFields = ['id', 'name', 'provider', 'users', 'aclRoleIds'] as const satisfies readonly (keyof XoGroup)[]
1010

1111
export const useXoGroupCollection = defineRemoteResource({
1212
url: `${BASE_URL}/groups?fields=${groupFields.join(',')}&ndjson=true`,

‎@xen-orchestra/web/src/modules/user/components/list/panel/cards/UserGroupsCard.vue‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
</template>
2121

2222
<script lang="ts" setup>
23-
import { useXoGroupCollection } from '@/modules/group/remote-ressources/use-xo-group-collection.ts'
23+
import { useXoGroupCollection } from '@/modules/group/remote-resources/use-xo-group-collection.ts'
2424
import type { FrontXoUser } from '@/modules/user/remote-resources/use-xo-user-collection.ts'
2525
import { useXoRoutes } from '@/shared/remote-resources/use-xo-routes.ts'
2626
import VtsIcon from '@core/components/icon/VtsIcon.vue'
Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
<template>
22
<RouterView v-slot="{ Component }">
33
<UserManagementHeader />
4-
<component :is="Component" :users />
4+
<component :is="Component" />
55
</RouterView>
66
</template>
77

88
<script lang="ts" setup>
99
import UserManagementHeader from '@/modules/admin/components/UserManagementHeader.vue'
10-
import { useXoUserCollection } from '@/modules/user/remote-resources/use-xo-user-collection.ts'
1110
import { useDefaultTab } from '@core/composables/default-tab.composable.ts'
1211
1312
useDefaultTab('/admin/user-management', 'users')
14-
15-
const { users } = useXoUserCollection()
1613
</script>

0 commit comments

Comments
 (0)