diff --git a/backend/internal/controller/oidc_controller.go b/backend/internal/controller/oidc_controller.go index d54d9a86..8adf1cb2 100644 --- a/backend/internal/controller/oidc_controller.go +++ b/backend/internal/controller/oidc_controller.go @@ -118,7 +118,7 @@ func (oc *OidcController) getClientHandler(c *gin.Context) error { // @Param pagination[limit] query int false "Number of items per page" default(20) // @Param sort[column] query string false "Column to sort by" // @Param sort[direction] query string false "Sort direction (asc or desc)" default("asc") -// @Success 200 {object} dto.Paginated[dto.OidcClientWithAllowedGroupsCountDto] +// @Success 200 {object} dto.Paginated[dto.OidcClientWithAllowedGroupsDto] // @Failure default {object} dto.ErrorDto "Error" // @Router /api/oidc/clients [get] func (oc *OidcController) listClientsHandler(c *gin.Context) error { @@ -131,22 +131,17 @@ func (oc *OidcController) listClientsHandler(c *gin.Context) error { } // Map the user groups to DTOs - var clientsDto = make([]dto.OidcClientWithAllowedGroupsCountDto, len(clients)) + var clientsDto = make([]dto.OidcClientWithAllowedGroupsDto, len(clients)) for i, client := range clients { - var clientDto dto.OidcClientWithAllowedGroupsCountDto + var clientDto dto.OidcClientWithAllowedGroupsDto if err := dto.MapStruct(client, &clientDto); err != nil { return err } clientDto.HasDarkLogo = client.HasDarkLogo() - - clientDto.AllowedUserGroupsCount, err = oc.oidcService.GetAllowedGroupsCountOfClient(c, client.ID) - if err != nil { - return err - } clientsDto[i] = clientDto } - c.JSON(http.StatusOK, dto.Paginated[dto.OidcClientWithAllowedGroupsCountDto]{ + c.JSON(http.StatusOK, dto.Paginated[dto.OidcClientWithAllowedGroupsDto]{ Data: clientsDto, Pagination: pagination, }) diff --git a/backend/internal/dto/oidc_dto.go b/backend/internal/dto/oidc_dto.go index 9ad737eb..5f541319 100644 --- a/backend/internal/dto/oidc_dto.go +++ b/backend/internal/dto/oidc_dto.go @@ -33,9 +33,9 @@ type OidcClientWithAllowedUserGroupsDto struct { AllowedUserGroups []UserGroupMinimalDto `json:"allowedUserGroups"` } -type OidcClientWithAllowedGroupsCountDto struct { +type OidcClientWithAllowedGroupsDto struct { OidcClientDto - AllowedUserGroupsCount int64 `json:"allowedUserGroupsCount"` + AllowedUserGroups []UserGroupMinimalDto `json:"allowedUserGroups"` } type OidcClientUpdateDto struct { diff --git a/backend/internal/service/oidc_service.go b/backend/internal/service/oidc_service.go index 4717136f..a653bd9b 100644 --- a/backend/internal/service/oidc_service.go +++ b/backend/internal/service/oidc_service.go @@ -124,14 +124,15 @@ func (s *OidcService) ListClients(ctx context.Context, name string, listRequestO query := s.db. WithContext(ctx). Preload("CreatedBy"). + Preload("AllowedUserGroups"). Model(&model.OidcClient{}) if name != "" { query = query.Where("name LIKE ?", "%"+name+"%") } - // As allowedUserGroupsCount is not a column, we need to manually sort it - if listRequestOptions.Sort.Column == "allowedUserGroupsCount" && utils.IsValidSortDirection(listRequestOptions.Sort.Direction) { + // Sort the allowed user groups relation by its row count because it is not an OIDC client column + if listRequestOptions.Sort.Column == "allowedUserGroups" && utils.IsValidSortDirection(listRequestOptions.Sort.Direction) { query = query.Select("oidc_clients.*, COUNT(oidc_clients_allowed_user_groups.oidc_client_id)"). Joins("LEFT JOIN oidc_clients_allowed_user_groups ON oidc_clients.id = oidc_clients_allowed_user_groups.oidc_client_id"). Group("oidc_clients.id"). @@ -629,22 +630,6 @@ func (s *OidcService) UpdateAllowedUserGroups(ctx context.Context, id string, in return client, nil } -func (s *OidcService) GetAllowedGroupsCountOfClient(ctx context.Context, id string) (int64, error) { - // We only perform select queries here, so we can rollback in all cases - tx := s.db.Begin() - defer func() { - tx.Rollback() - }() - - client, err := s.getClientInternal(ctx, id, tx, false) - if err != nil { - return 0, err - } - - count := tx.WithContext(ctx).Model(&client).Association("AllowedUserGroups").Count() - return count, nil -} - func (s *OidcService) ListAuthorizedClients(ctx context.Context, userID string, listRequestOptions utils.ListRequestOptions) ([]model.UserAuthorizedOidcClient, utils.PaginationResponse, error) { tx := s.db.Begin() defer func() { diff --git a/frontend/src/lib/components/table/advanced-table-column-selection.svelte b/frontend/src/lib/components/table/advanced-table-column-selection.svelte index ae247924..d94a8b6a 100644 --- a/frontend/src/lib/components/table/advanced-table-column-selection.svelte +++ b/frontend/src/lib/components/table/advanced-table-column-selection.svelte @@ -28,20 +28,20 @@ {#each columns as column (column)} {#if column.label} - { - const key = column.column ?? column.key!; - if (v) { - selectedColumns = [...selectedColumns, key]; - } else { - selectedColumns = selectedColumns.filter((c) => c !== key); - } - }} - > - {column.label} - + { + const key = column.column ?? column.key!; + if (v) { + selectedColumns = [...selectedColumns, key]; + } else { + selectedColumns = selectedColumns.filter((c) => c !== key); + } + }} + > + {column.label} + {/if} {/each} diff --git a/frontend/src/lib/components/ui/scroll-area/index.ts b/frontend/src/lib/components/ui/scroll-area/index.ts new file mode 100644 index 00000000..d5468067 --- /dev/null +++ b/frontend/src/lib/components/ui/scroll-area/index.ts @@ -0,0 +1,10 @@ +import Scrollbar from './scroll-area-scrollbar.svelte'; +import Root from './scroll-area.svelte'; + +export { + Root, + Scrollbar, + //, + Root as ScrollArea, + Scrollbar as ScrollAreaScrollbar +}; diff --git a/frontend/src/lib/components/ui/scroll-area/scroll-area-scrollbar.svelte b/frontend/src/lib/components/ui/scroll-area/scroll-area-scrollbar.svelte new file mode 100644 index 00000000..d5092f8a --- /dev/null +++ b/frontend/src/lib/components/ui/scroll-area/scroll-area-scrollbar.svelte @@ -0,0 +1,30 @@ + + + + {@render children?.()} + + diff --git a/frontend/src/lib/components/ui/scroll-area/scroll-area.svelte b/frontend/src/lib/components/ui/scroll-area/scroll-area.svelte new file mode 100644 index 00000000..3a9fd4a5 --- /dev/null +++ b/frontend/src/lib/components/ui/scroll-area/scroll-area.svelte @@ -0,0 +1,43 @@ + + + + + {@render children?.()} + + {#if orientation === 'vertical' || orientation === 'both'} + + {/if} + {#if orientation === 'horizontal' || orientation === 'both'} + + {/if} + + diff --git a/frontend/src/lib/services/oidc-service.ts b/frontend/src/lib/services/oidc-service.ts index 97103a39..c108f56d 100644 --- a/frontend/src/lib/services/oidc-service.ts +++ b/frontend/src/lib/services/oidc-service.ts @@ -11,8 +11,8 @@ import type { OidcClientSecret, OidcClientSecretCreated, OidcClientUpdate, + OidcClientWithAllowedGroups, OidcClientWithAllowedUserGroups, - OidcClientWithAllowedUserGroupsCount, OidcDeviceCodeInfo } from '$lib/types/oidc.type'; import type { ScimServiceProvider } from '$lib/types/scim.type'; @@ -38,7 +38,7 @@ class OidcService extends APIService { const res = await this.api.get('/oidc/clients', { params: options }); - return res.data as Paginated; + return res.data as Paginated; }; createClient = async (client: OidcClientCreate) => diff --git a/frontend/src/lib/types/oidc.type.ts b/frontend/src/lib/types/oidc.type.ts index 4df0dacc..399b6e74 100644 --- a/frontend/src/lib/types/oidc.type.ts +++ b/frontend/src/lib/types/oidc.type.ts @@ -1,4 +1,4 @@ -import type { UserGroup } from './user-group.type'; +import type { UserGroup, UserGroupMinimal } from './user-group.type'; export type OidcClientType = 'standard' | 'cimd'; @@ -74,8 +74,8 @@ export type OidcClientWithAllowedUserGroups = OidcClient & { allowedUserGroups: UserGroup[]; }; -export type OidcClientWithAllowedUserGroupsCount = OidcClient & { - allowedUserGroupsCount: number; +export type OidcClientWithAllowedGroups = OidcClient & { + allowedUserGroups: UserGroupMinimal[]; }; export type OidcClientUpdate = Omit< diff --git a/frontend/src/routes/settings/admin/oidc-clients/oidc-client-list.svelte b/frontend/src/routes/settings/admin/oidc-clients/oidc-client-list.svelte index b5c34cca..5057d60c 100644 --- a/frontend/src/routes/settings/admin/oidc-clients/oidc-client-list.svelte +++ b/frontend/src/routes/settings/admin/oidc-clients/oidc-client-list.svelte @@ -3,13 +3,15 @@ import { openConfirmDialog } from '$lib/components/confirm-dialog/'; import ImageBox from '$lib/components/image-box.svelte'; import AdvancedTable from '$lib/components/table/advanced-table.svelte'; + import { ScrollArea } from '$lib/components/ui/scroll-area'; + import * as Tooltip from '$lib/components/ui/tooltip'; import { m } from '$lib/paraglide/messages'; import OIDCService from '$lib/services/oidc-service'; import type { AdvancedTableColumn, CreateAdvancedTableActions } from '$lib/types/advanced-table.type'; - import type { OidcClient, OidcClientWithAllowedUserGroupsCount } from '$lib/types/oidc.type'; + import type { OidcClient, OidcClientWithAllowedGroups } from '$lib/types/oidc.type'; import { cachedOidcClientLogo } from '$lib/utils/cached-image-util'; import { encodeClientIdParam } from '$lib/utils/client-id-util'; import { axiosErrorToast } from '$lib/utils/error-util'; @@ -18,7 +20,7 @@ import { toast } from 'svelte-sonner'; const oidcService = new OIDCService(); - let tableRef: AdvancedTable; + let tableRef: AdvancedTable; export function refresh() { return tableRef?.refresh(); @@ -36,15 +38,15 @@ { label: m.client_type_metadata_document(), value: 'cimd' } ]; - const columns: AdvancedTableColumn[] = [ + const columns: AdvancedTableColumn[] = [ { label: 'ID', column: 'id', hidden: true }, { label: m.logo(), key: 'logo', cell: LogoCell }, { label: m.name(), column: 'name', sortable: true }, { label: m.oidc_allowed_group_count(), - column: 'allowedUserGroupsCount', + column: 'allowedUserGroups', sortable: true, - value: (item) => (item.isGroupRestricted ? item.allowedUserGroupsCount : '-') + cell: AllowedGroupCountCell }, { label: m.restricted(), @@ -93,7 +95,7 @@ } ]; - const actions: CreateAdvancedTableActions = (client) => [ + const actions: CreateAdvancedTableActions = (client) => [ { label: m.edit(), primary: true, @@ -145,7 +147,35 @@ } -{#snippet LogoCell({ item }: { item: OidcClientWithAllowedUserGroupsCount })} +{#snippet AllowedGroupCountCell({ item }: { item: OidcClientWithAllowedGroups })} + {#if !item.isGroupRestricted} + - + {:else if item.allowedUserGroups.length === 0} + {item.allowedUserGroups.length} + {:else} + + + + {item.allowedUserGroups.length} + + + + + {#each item.allowedUserGroups as group (group.id)} + {group.friendlyName} + {/each} + + + + + + {/if} +{/snippet} + +{#snippet LogoCell({ item }: { item: OidcClientWithAllowedGroups })} {#if item.hasLogo} ; + let tableRef: AdvancedTable; export function refresh() { return tableRef?.refresh(); @@ -25,16 +25,16 @@ const isLightMode = $derived(mode.current === 'light'); - const columns: AdvancedTableColumn[] = [ + const columns: AdvancedTableColumn[] = [ { label: 'ID', column: 'id', hidden: true }, { label: m.logo(), key: 'logo', cell: LogoCell }, { label: m.name(), column: 'name', sortable: true }, { label: m.oidc_allowed_group_count(), - column: 'allowedUserGroupsCount', + column: 'allowedUserGroups', sortable: true, - value: (item) => (item.isGroupRestricted ? item.allowedUserGroupsCount : '-') + value: (item) => (item.isGroupRestricted ? item.allowedUserGroups.length : '-') }, { label: m.restricted(),