diff --git a/@xen-orchestra/web-core/lib/components/table/cells/VtsUserNameCell.vue b/@xen-orchestra/web-core/lib/components/table/cells/VtsUserNameCell.vue new file mode 100644 index 0000000000..f82119b622 --- /dev/null +++ b/@xen-orchestra/web-core/lib/components/table/cells/VtsUserNameCell.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/@xen-orchestra/web-core/lib/components/ui/head-bar/UiHeadBar.vue b/@xen-orchestra/web-core/lib/components/ui/head-bar/UiHeadBar.vue index 5d620254e5..fb9c23646e 100644 --- a/@xen-orchestra/web-core/lib/components/ui/head-bar/UiHeadBar.vue +++ b/@xen-orchestra/web-core/lib/components/ui/head-bar/UiHeadBar.vue @@ -3,7 +3,7 @@
- +

diff --git a/@xen-orchestra/web-core/lib/locales/en.json b/@xen-orchestra/web-core/lib/locales/en.json index b31fe3063e..669407896b 100644 --- a/@xen-orchestra/web-core/lib/locales/en.json +++ b/@xen-orchestra/web-core/lib/locales/en.json @@ -156,6 +156,7 @@ "alarm-type:sr_io_throughput_total_per_host": "SR IO throughput total per host exceeds @:n-percent", "alarm-type:unknown": "Unknown alarm type", "alarms": "Alarms", + "alert-user-roles": "A user can be assigned roles directly or through groups. Roles inherited from a group cannot be removed at the user level. To revoke them, update the group's roles or remove the user from the group. You can always add individual roles on top of inherited ones.", "all": "All", "all-done!": "All done!", "all-good!": "All good!", @@ -355,6 +356,7 @@ "duplicating-vm": "Duplicating VM", "duplication-method": "Duplication method", "duration": "Duration", + "email": "Email", "empty": "Empty", "enabled": "Enabled", "end-date": "End date", @@ -581,6 +583,7 @@ "language": "Language", "language-preferences": "Language preferences", "last": "Last", + "last-login": "Last login", "last-n-backup-archives": "Last backup archive | Last {n} backup archives", "last-n-backup-runs": "Last backup run | Last {n} backup runs", "last-n-runs": "Last run | Last {n} runs", @@ -731,6 +734,7 @@ "no-custom-field-detected": "No custom fields detected", "no-data": "No data", "no-data-to-calculate": "No data to calculate", + "no-group-attached": "No group attached", "no-host-attached": "No hosts attached", "no-host-detected": "No hosts detected", "no-job-vm": "This VM isn’t part of any running job.", @@ -740,6 +744,7 @@ "no-pools-detected": "No pools detected", "no-replicated-vm": "This VM isn’t being replicated. Configure a replication job to enable it.", "no-result": "No results", + "no-role-applied": "No role applied", "no-schedule-available": "No schedules available", "no-selected-vm-can-be-exported": "No selected VM can be exported", "no-selected-vm-can-be-migrated": "No selected VM can be migrated", @@ -748,6 +753,7 @@ "no-storage-repository-detected": "No storage repositories detected", "no-task": "No tasks", "no-task-detected": "No tasks detected", + "no-user-detected": "No users detected", "no-vdi-attached": "No VDIs attached", "no-vdi-detected": "No VDIs detected", "no-vif-detected": "No VIFs detected", @@ -853,6 +859,7 @@ "protocol": "Protocol", "protocol:port": "@:{'protocol'}:@:{'port'}", "provider-solutions": "{provider} solutions", + "providers": "Providers", "provisioning": "Provisioning", "proxy": "Proxy", "proxy-url": "Proxy URL", @@ -920,6 +927,7 @@ "rest-api": "REST API", "retry": "Retry", "roles": "Roles", + "roles-applied": "Roles applied", "root-by-default": "\"root\" by default.", "rule-type": "Rule type", "run": "Run", @@ -1109,6 +1117,7 @@ "used-for-backup": "Used for backup", "used-space": "Used space", "used-space-on-sr": "Used space on SR", + "user": "User", "user-config": "User config", "user-management": "User management", "username": "Username", @@ -1213,6 +1222,7 @@ "xoa-deploy-retry": "Try again to deploy XOA", "xoa-deploy-successful!": "XOA deployment successful!", "xoa-ip": "XOA IP address", + "xoa-manager": "XOA manager", "xoa-password-confirm-different": "XOA password confirmation is different", "xoa-ssh-account": "XOA SSH account", "xoa-ssh-password-confirm-different": "SSH password confirmation is different", diff --git a/@xen-orchestra/web-core/lib/locales/fr.json b/@xen-orchestra/web-core/lib/locales/fr.json index 06514cf8ae..3eb9a3aae9 100644 --- a/@xen-orchestra/web-core/lib/locales/fr.json +++ b/@xen-orchestra/web-core/lib/locales/fr.json @@ -156,6 +156,7 @@ "alarm-type:sr_io_throughput_total_per_host": "Le débit total de l'IO SR par hôte dépasse @:n-percent", "alarm-type:unknown": "Type d'alarme inconnu", "alarms": "Alarmes", + "alert-user-roles": "Des rôles peuvent être attribués à un utilisateur directement ou via des groupes. Les rôles hérités d’un groupe ne peuvent pas être supprimés au niveau de l’utilisateur. Pour les révoquer, mettez à jour les rôles du groupe ou retirez l’utilisateur du groupe. Vous pouvez toujours ajouter des rôles individuels aux rôles hérités.", "all": "Tout", "all-done!": "Tout est terminé !", "all-good!": "Tout va bien !", @@ -355,6 +356,7 @@ "duplicating-vm": "Duplication de la VM", "duplication-method": "Méthode de duplication", "duration": "Durée", + "email": "Email", "empty": "Vide", "enabled": "Activé", "end-date": "Date de fin", @@ -581,6 +583,7 @@ "language": "Langue", "language-preferences": "Choix de la langue", "last": "Dernier", + "last-login": "Dernière connexion", "last-n-backup-archives": "Dernier run d'archivage | {n} derniers archivages", "last-n-backup-runs": "Dernier run de sauvegarde | {n} dernières sauvegardes", "last-n-runs": "Dernier run | {n} derniers runs", @@ -731,6 +734,7 @@ "no-custom-field-detected": "Aucun champ personnalisé détecté", "no-data": "Aucune donnée", "no-data-to-calculate": "Aucune donnée à calculer", + "no-group-attached": "Pas de groupe attaché", "no-host-attached": "Aucun hôte attaché", "no-host-detected": "Aucun hôte détecté", "no-job-vm": "Cette VM ne fait partie d'aucun job actif.", @@ -740,6 +744,7 @@ "no-pools-detected": "Aucun pool détecté", "no-replicated-vm": "Cette VM n'est pas répliquée. Configurez une tâche de réplication pour l'activer.", "no-result": "Aucun résultat", + "no-role-applied": "Pas de rôle appliqué", "no-schedule-available": "Aucune planification disponible", "no-selected-vm-can-be-exported": "Aucune VM sélectionnée ne peut être exportée", "no-selected-vm-can-be-migrated": "Aucune VM sélectionnée ne peut être migrée", @@ -748,6 +753,7 @@ "no-storage-repository-detected": "Aucun dépôt de stockage détecté", "no-task": "Aucune tâche", "no-task-detected": "Aucune tâche détectée", + "no-user-detected": "Aucun utilisateur détecté", "no-vdi-attached": "Aucun VDI attaché", "no-vdi-detected": "Aucun VDI détecté", "no-vif-detected": "Aucun VIF détecté", @@ -853,6 +859,7 @@ "protocol": "Protocole", "protocol:port": "@:{'protocol'}:@:{'port'}", "provider-solutions": "Solutions {provider}", + "providers": "Fournisseurs", "provisioning": "Provisionnement", "proxy": "Proxy", "proxy-url": "URL du Proxy", @@ -920,6 +927,7 @@ "rest-api": "API REST", "retry": "Réessayer", "roles": "Rôles", + "roles-applied": "Rôles appliqués", "root-by-default": "\"root\" par défaut.", "rule-type": "Type de règle", "run": "Run", @@ -1109,6 +1117,7 @@ "used-for-backup": "Utilisé pour la sauvegarde", "used-space": "Espace utilisé", "used-space-on-sr": "Espace utilisé sur le SR", + "user": "Utilisateur", "user-config": "Configuration utilisateur", "user-management": "Gestion des utilisateurs", "username": "Nom d'utilisateur", @@ -1213,6 +1222,7 @@ "xoa-deploy-retry": "Ré-essayer de déployer une XOA", "xoa-deploy-successful!": "XOA deployée avec succès !", "xoa-ip": "Adresse IP XOA", + "xoa-manager": "Manager XOA", "xoa-password-confirm-different": "La confirmation du mot de passe XOA est différente", "xoa-ssh-account": "Compte SSH de la XOA", "xoa-ssh-password-confirm-different": "La confirmation du mot de passe SSH est différente", diff --git a/@xen-orchestra/web-core/lib/tables/column-definitions/user-name-column.ts b/@xen-orchestra/web-core/lib/tables/column-definitions/user-name-column.ts new file mode 100644 index 0000000000..87f6d84660 --- /dev/null +++ b/@xen-orchestra/web-core/lib/tables/column-definitions/user-name-column.ts @@ -0,0 +1,10 @@ +import VtsUserNameCell from '@core/components/table/cells/VtsUserNameCell.vue' +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) => ({ + renderHead: () => renderHeadCell(config?.headerLabel), + renderBody: (content: string, props?: { href?: string }) => h(VtsUserNameCell, { content, href: props?.href }), +})) diff --git a/@xen-orchestra/web-core/lib/tables/column-sets/user-columns.ts b/@xen-orchestra/web-core/lib/tables/column-sets/user-columns.ts new file mode 100644 index 0000000000..dacbb5e214 --- /dev/null +++ b/@xen-orchestra/web-core/lib/tables/column-sets/user-columns.ts @@ -0,0 +1,16 @@ +import { defineColumns } from '@core/packages/table/define-columns.ts' +import { useSelectItemColumn } from '@core/tables/column-definitions/select-item-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 { useI18n } from 'vue-i18n' + +export const useUserColumns = defineColumns(() => { + const { t } = useI18n() + + return { + username: useUserNameColumn({ headerLabel: () => t('username') }), + email: useTruncatedTextColumn({ headerLabel: () => t('email') }), + providers: useTruncatedTextColumn({ headerLabel: () => t('providers') }), + selectItem: useSelectItemColumn(), + } +}) diff --git a/@xen-orchestra/web/src/layouts/AppLayout.vue b/@xen-orchestra/web/src/layouts/AppLayout.vue index 1cf5dfde73..08e90c8e9e 100644 --- a/@xen-orchestra/web/src/layouts/AppLayout.vue +++ b/@xen-orchestra/web/src/layouts/AppLayout.vue @@ -67,7 +67,7 @@ diff --git a/@xen-orchestra/web/src/modules/group/remote-ressources/use-xo-group-collection.ts b/@xen-orchestra/web/src/modules/group/remote-ressources/use-xo-group-collection.ts new file mode 100644 index 0000000000..49ab7586b2 --- /dev/null +++ b/@xen-orchestra/web/src/modules/group/remote-ressources/use-xo-group-collection.ts @@ -0,0 +1,20 @@ +import { useXoCollectionState } from '@/shared/composables/xo-collection-state/use-xo-collection-state.ts' +import { BASE_URL } from '@/shared/utils/fetch.util.ts' +import { defineRemoteResource } from '@core/packages/remote-resource/define-remote-resource.ts' +import type { XoGroup } from '@vates/types' + +export type FrontXoGroup = Pick + +const groupFields = ['id', 'name'] as const satisfies readonly (keyof XoGroup)[] + +export const useXoGroupCollection = defineRemoteResource({ + url: `${BASE_URL}/groups?fields=${groupFields.join(',')}`, + + initialData: () => [] as FrontXoGroup[], + state: (groups, context) => { + return useXoCollectionState(groups, { + context, + baseName: 'group', + }) + }, +}) diff --git a/@xen-orchestra/web/src/modules/user/components/list/UsersTable.vue b/@xen-orchestra/web/src/modules/user/components/list/UsersTable.vue new file mode 100644 index 0000000000..e4b55dbaac --- /dev/null +++ b/@xen-orchestra/web/src/modules/user/components/list/UsersTable.vue @@ -0,0 +1,86 @@ + + + diff --git a/@xen-orchestra/web/src/modules/user/components/list/panel/UserSidePanel.vue b/@xen-orchestra/web/src/modules/user/components/list/panel/UserSidePanel.vue new file mode 100644 index 0000000000..0695810c2c --- /dev/null +++ b/@xen-orchestra/web/src/modules/user/components/list/panel/UserSidePanel.vue @@ -0,0 +1,25 @@ + + + diff --git a/@xen-orchestra/web/src/modules/user/components/list/panel/cards/UserGroupsCard.vue b/@xen-orchestra/web/src/modules/user/components/list/panel/cards/UserGroupsCard.vue new file mode 100644 index 0000000000..5a394720f7 --- /dev/null +++ b/@xen-orchestra/web/src/modules/user/components/list/panel/cards/UserGroupsCard.vue @@ -0,0 +1,71 @@ + + + + + diff --git a/@xen-orchestra/web/src/modules/user/components/list/panel/cards/UserInfosCard.vue b/@xen-orchestra/web/src/modules/user/components/list/panel/cards/UserInfosCard.vue new file mode 100644 index 0000000000..67fcdf22fc --- /dev/null +++ b/@xen-orchestra/web/src/modules/user/components/list/panel/cards/UserInfosCard.vue @@ -0,0 +1,115 @@ + + + + + diff --git a/@xen-orchestra/web/src/modules/user/components/list/panel/cards/UserRolesCard.vue b/@xen-orchestra/web/src/modules/user/components/list/panel/cards/UserRolesCard.vue new file mode 100644 index 0000000000..67532983bd --- /dev/null +++ b/@xen-orchestra/web/src/modules/user/components/list/panel/cards/UserRolesCard.vue @@ -0,0 +1,36 @@ + + + + + diff --git a/@xen-orchestra/web/src/modules/user/remote-resources/use-xo-user-authentication-tokens-collection.ts b/@xen-orchestra/web/src/modules/user/remote-resources/use-xo-user-authentication-tokens-collection.ts new file mode 100644 index 0000000000..bd3c235c19 --- /dev/null +++ b/@xen-orchestra/web/src/modules/user/remote-resources/use-xo-user-authentication-tokens-collection.ts @@ -0,0 +1,14 @@ +import type { FrontXoUser } from '@/modules/user/remote-resources/use-xo-user-collection.ts' +import { BASE_URL } from '@/shared/utils/fetch.util.ts' +import { defineRemoteResource } from '@core/packages/remote-resource/define-remote-resource.ts' +import type { XoAuthenticationToken } from '@vates/types' + +export const useXoUserAuthenticationTokensCollection = defineRemoteResource({ + url: (userId: FrontXoUser['id']) => `${BASE_URL}/users/${userId}/authentication_tokens`, + initialData: () => [] as XoAuthenticationToken[], + state: (userAuthenticationTokens, context) => ({ + userAuthenticationTokens, + areUserAuthenticationTokensReady: context.isReady, + areUserAuthenticationTokensFetchError: context.hasError, + }), +}) diff --git a/@xen-orchestra/web/src/modules/user/remote-resources/use-xo-user-collection.ts b/@xen-orchestra/web/src/modules/user/remote-resources/use-xo-user-collection.ts index f7fb59c7bd..64316316df 100644 --- a/@xen-orchestra/web/src/modules/user/remote-resources/use-xo-user-collection.ts +++ b/@xen-orchestra/web/src/modules/user/remote-resources/use-xo-user-collection.ts @@ -5,7 +5,14 @@ import type { XoUser } from '@vates/types' export type FrontXoUser = Pick -const userFields = ['id', 'name', 'permission', 'email', 'groups'] as const satisfies readonly (keyof XoUser)[] +const userFields = [ + 'id', + 'name', + 'permission', + 'email', + 'groups', + 'authProviders', +] as const satisfies readonly (keyof XoUser)[] export const useXoUserCollection = defineRemoteResource({ url: `${BASE_URL}/users?fields=${userFields.join(',')}`, diff --git a/@xen-orchestra/web/src/pages/admin/user-management.vue b/@xen-orchestra/web/src/pages/admin/user-management.vue new file mode 100644 index 0000000000..89d0fcaeb3 --- /dev/null +++ b/@xen-orchestra/web/src/pages/admin/user-management.vue @@ -0,0 +1,16 @@ + + + diff --git a/@xen-orchestra/web/src/pages/admin/user-management/users.vue b/@xen-orchestra/web/src/pages/admin/user-management/users.vue new file mode 100644 index 0000000000..56066c75e6 --- /dev/null +++ b/@xen-orchestra/web/src/pages/admin/user-management/users.vue @@ -0,0 +1,51 @@ + + + + + diff --git a/@xen-orchestra/web/src/route-map.d.ts b/@xen-orchestra/web/src/route-map.d.ts index 4c387da76a..ee64e0244e 100644 --- a/@xen-orchestra/web/src/route-map.d.ts +++ b/@xen-orchestra/web/src/route-map.d.ts @@ -91,6 +91,20 @@ declare module 'vue-router/auto-routes' { { path: ParamValue }, | never >, + '/admin/user-management': RouteRecordInfo< + '/admin/user-management', + '/admin/user-management', + Record, + Record, + | '/admin/user-management/users' + >, + '/admin/user-management/users': RouteRecordInfo< + '/admin/user-management/users', + '/admin/user-management/users', + Record, + Record, + | never + >, '/backup/[id]': RouteRecordInfo< '/backup/[id]', '/backup/:id', @@ -511,6 +525,19 @@ declare module 'vue-router/auto-routes' { views: | never } + 'src/pages/admin/user-management.vue': { + routes: + | '/admin/user-management' + | '/admin/user-management/users' + views: + | 'default' + } + 'src/pages/admin/user-management/users.vue': { + routes: + | '/admin/user-management/users' + views: + | never + } 'src/pages/backup/[id].vue': { routes: | '/backup/[id]' diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index d7f346af38..45b7fb90b4 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -45,6 +45,7 @@ - [REST API] Expose `/rest/v0/acl-roles/:id/users` (PR [#10085](https://github.com/vatesfr/xen-orchestra/pull/10085)) - [REST API] Expose `/rest/v0/acl-roles/:id/groups` (PR [#10085](https://github.com/vatesfr/xen-orchestra/pull/10085)) - [XO6] Fix some design inconsistency between pages (PR [#10109](https://github.com/vatesfr/xen-orchestra/pull/10109)) +- [XO6/Users] Add Users Table (PR [#10029](https://github.com/vatesfr/xen-orchestra/pull/10029)) ### Bug fixes