feat: list allowed groups when hovering over allowed group count

This commit is contained in:
Kyle Mendell
2026-08-10 21:18:05 -05:00
parent 155a1fcba0
commit 4afb2bd635
8 changed files with 120 additions and 24 deletions
@@ -137,11 +137,7 @@ func (oc *OidcController) listClientsHandler(c *gin.Context) error {
return err
}
clientDto.HasDarkLogo = client.HasDarkLogo()
clientDto.AllowedUserGroupsCount, err = oc.oidcService.GetAllowedGroupsCountOfClient(c, client.ID)
if err != nil {
return err
}
clientDto.AllowedUserGroupsCount = int64(len(client.AllowedUserGroups))
clientsDto[i] = clientDto
}
+2 -1
View File
@@ -35,7 +35,8 @@ type OidcClientWithAllowedUserGroupsDto struct {
type OidcClientWithAllowedGroupsCountDto struct {
OidcClientDto
AllowedUserGroupsCount int64 `json:"allowedUserGroupsCount"`
AllowedUserGroups []UserGroupMinimalDto `json:"allowedUserGroups"`
AllowedUserGroupsCount int64 `json:"allowedUserGroupsCount"`
}
type OidcClientUpdateDto struct {
+1 -16
View File
@@ -124,6 +124,7 @@ func (s *OidcService) ListClients(ctx context.Context, name string, listRequestO
query := s.db.
WithContext(ctx).
Preload("CreatedBy").
Preload("AllowedUserGroups").
Model(&model.OidcClient{})
if name != "" {
@@ -627,22 +628,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() {
@@ -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
};
@@ -0,0 +1,30 @@
<script lang="ts">
import { ScrollArea as ScrollAreaPrimitive } from 'bits-ui';
import { cn, type WithoutChild } from '$lib/utils/style.js';
let {
ref = $bindable(null),
class: className,
orientation = 'vertical',
children,
...restProps
}: WithoutChild<ScrollAreaPrimitive.ScrollbarProps> = $props();
</script>
<ScrollAreaPrimitive.Scrollbar
bind:ref
data-slot="scroll-area-scrollbar"
data-orientation={orientation}
{orientation}
class={cn(
'data-horizontal:h-2.5 data-horizontal:flex-col data-horizontal:border-t data-horizontal:border-t-transparent data-vertical:h-full data-vertical:w-2.5 data-vertical:border-l data-vertical:border-l-transparent flex touch-none p-px transition-colors select-none',
className
)}
{...restProps}
>
{@render children?.()}
<ScrollAreaPrimitive.Thumb
data-slot="scroll-area-thumb"
class="rounded-full relative flex-1 bg-border"
/>
</ScrollAreaPrimitive.Scrollbar>
@@ -0,0 +1,43 @@
<script lang="ts">
import { ScrollArea as ScrollAreaPrimitive } from 'bits-ui';
import { cn, type WithoutChild } from '$lib/utils/style.js';
import { Scrollbar } from './index.js';
let {
ref = $bindable(null),
viewportRef = $bindable(null),
class: className,
orientation = 'vertical',
scrollbarXClasses = '',
scrollbarYClasses = '',
children,
...restProps
}: WithoutChild<ScrollAreaPrimitive.RootProps> & {
orientation?: 'vertical' | 'horizontal' | 'both' | undefined;
scrollbarXClasses?: string | undefined;
scrollbarYClasses?: string | undefined;
viewportRef?: HTMLElement | null;
} = $props();
</script>
<ScrollAreaPrimitive.Root
bind:ref
data-slot="scroll-area"
class={cn('relative', className)}
{...restProps}
>
<ScrollAreaPrimitive.Viewport
bind:ref={viewportRef}
data-slot="scroll-area-viewport"
class="cn-scroll-area-viewport size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1"
>
{@render children?.()}
</ScrollAreaPrimitive.Viewport>
{#if orientation === 'vertical' || orientation === 'both'}
<Scrollbar orientation="vertical" class={scrollbarYClasses} />
{/if}
{#if orientation === 'horizontal' || orientation === 'both'}
<Scrollbar orientation="horizontal" class={scrollbarXClasses} />
{/if}
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
+2 -1
View File
@@ -1,4 +1,4 @@
import type { UserGroup } from './user-group.type';
import type { UserGroup, UserGroupMinimal } from './user-group.type';
export type OidcClientType = 'standard' | 'cimd';
@@ -75,6 +75,7 @@ export type OidcClientWithAllowedUserGroups = OidcClient & {
};
export type OidcClientWithAllowedUserGroupsCount = OidcClient & {
allowedUserGroups: UserGroupMinimal[];
allowedUserGroupsCount: number;
};
@@ -3,6 +3,8 @@
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 {
@@ -44,7 +46,7 @@
label: m.oidc_allowed_group_count(),
column: 'allowedUserGroupsCount',
sortable: true,
value: (item) => (item.isGroupRestricted ? item.allowedUserGroupsCount : '-')
cell: AllowedGroupCountCell
},
{
label: m.restricted(),
@@ -145,6 +147,34 @@
}
</script>
{#snippet AllowedGroupCountCell({ item }: { item: OidcClientWithAllowedUserGroupsCount })}
{#if !item.isGroupRestricted}
-
{:else if item.allowedUserGroups.length === 0}
{item.allowedUserGroupsCount}
{:else}
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger class="cursor-default underline decoration-dotted underline-offset-4">
{item.allowedUserGroupsCount}
</Tooltip.Trigger>
<Tooltip.Content side="right" class="flex-col items-start">
<ScrollArea
class="[&>[data-slot=scroll-area-viewport]]:max-h-48"
scrollbarYClasses="[&>[data-slot=scroll-area-thumb]]:bg-background/40"
>
<div class="flex flex-col gap-0.5 pr-3">
{#each item.allowedUserGroups as group (group.id)}
<span>{group.friendlyName}</span>
{/each}
</div>
</ScrollArea>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>
{/if}
{/snippet}
{#snippet LogoCell({ item }: { item: OidcClientWithAllowedUserGroupsCount })}
{#if item.hasLogo}
<ImageBox