From 6e0e31c694fb5faa2e5f0a355dd1fca96aad6463 Mon Sep 17 00:00:00 2001 From: Edouard Vanbelle Date: Sun, 19 Jul 2026 16:41:10 +0200 Subject: [PATCH] admin > users colunms --- .../src/lib/components/GroupVignette.svelte | 3 +- frontend/src/routes/admin/+page.svelte | 121 ++++++++++++------ 2 files changed, 85 insertions(+), 39 deletions(-) diff --git a/frontend/src/lib/components/GroupVignette.svelte b/frontend/src/lib/components/GroupVignette.svelte index 7aa5bf37..cd037789 100644 --- a/frontend/src/lib/components/GroupVignette.svelte +++ b/frontend/src/lib/components/GroupVignette.svelte @@ -22,6 +22,7 @@ import { resolveRecipient } from '$lib/api/endpoints/recipients'; import { resolveUser } from '$lib/api/endpoints/users'; import { listMembers, type GroupMember } from '$lib/api/endpoints/groups'; + import { SvelteMap } from 'svelte/reactivity'; interface Props { groupId: string; @@ -32,7 +33,7 @@ // Module-scoped cache of resolved members per group. Chips render N // times on a busy /shared page; the shared cache avoids N × HTTP. - const memberCache = new Map>(); + const memberCache = new SvelteMap>(); function loadMembers(id: string): Promise { let p = memberCache.get(id); if (!p) { diff --git a/frontend/src/routes/admin/+page.svelte b/frontend/src/routes/admin/+page.svelte index 1d4a4562..b25932b8 100644 --- a/frontend/src/routes/admin/+page.svelte +++ b/frontend/src/routes/admin/+page.svelte @@ -1295,9 +1295,7 @@ const quota_bytes = result.unlimited ? null : result.bytes; const persisted = await updateDriveQuota(driveQuotaModal.driveId, quota_bytes); const driveId = driveQuotaModal.driveId; - drivesList = drivesList.map((d) => - d.id === driveId ? { ...d, quota_bytes: persisted } : d - ); + drivesList = drivesList.map((d) => (d.id === driveId ? { ...d, quota_bytes: persisted } : d)); // Sibling surfaces (sidebar picker, breadcrumb) read the // cached `GET /api/drives`. Mirrors the policies-modal // pattern above. @@ -2267,34 +2265,50 @@ - - {#if u.role === 'admin'}{/if} - {u.role} - - {#if u.is_external} - - - - {t('admin.external_user', 'external')} + +
+ + {#if u.role === 'admin'}{/if} + {u.role} - {/if} + {#if u.is_external} + + + + {t('admin.external_user', 'external')} + + {/if} +
- + + {#if isOidcUser(u)} - {u.auth_provider} + {u.auth_provider} {:else} {t('admin.local', 'local')} @@ -2313,7 +2327,13 @@ usage bar with `0 / 0` reads as "over quota" visually and is misleading; show an em-dash instead. --> - — + — {:else}
@@ -2347,10 +2367,7 @@ class="icon-btn icon-btn--success" data-testid={`admin-user-promote-${u.id}`} title={t('admin.promote_to_internal_title', 'Promote to internal user')} - aria-label={t( - 'admin.promote_to_internal_title', - 'Promote to internal user' - )} + aria-label={t('admin.promote_to_internal_title', 'Promote to internal user')} onclick={() => promoteExternal(u)} > @@ -2498,9 +2515,7 @@ ? d.quota_bytes : null} {@const pct = - effectiveQuota !== null - ? Math.min(100, (d.used_bytes / effectiveQuota) * 100) - : null} + effectiveQuota !== null ? Math.min(100, (d.used_bytes / effectiveQuota) * 100) : null}
@@ -3231,9 +3246,7 @@ data-testid="admin-delete-user-confirm-btn" disabled={!deleteUserEmailMatches || deleteUserBusy} > - {deleteUserBusy - ? t('admin.deleting', 'Deleting…') - : t('admin.delete_title', 'Delete user')} + {deleteUserBusy ? t('admin.deleting', 'Deleting…') : t('admin.delete_title', 'Delete user')} {/snippet} @@ -3644,12 +3657,44 @@ colour reuses `--color-warning-*` because "external" is the same "attention needed" family as "you". */ .badge--external { - margin-left: var(--space-1); background: var(--color-warning-bg); color: var(--color-warning-text); text-transform: uppercase; } + /* Wrapper for the (role + optional external) badge pair in the + users table. Row-flex + nowrap keeps the two on a single line + when the column narrows; the sibling badges themselves also + set `white-space: nowrap` so the ".EXTERNAL" label never + splits mid-word either. */ + .role-badges { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + align-items: center; + gap: var(--space-1, 0.25rem); + } + .role-badges .badge { + white-space: nowrap; + } + + /* Authentication column. The OIDC provider label is free-form + and can widen the column arbitrarily on hosts that use long + identifiers ("google-workspace-prod"). Cap the column width + and ellipsis the label so the row layout stays balanced; the + full provider is still reachable via the badge tooltip. */ + .auth-cell { + max-width: 12ch; + } + .auth-cell .badge__label { + display: inline-block; + max-width: 8ch; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + vertical-align: bottom; + } + /* Enabled/disabled feature flag indicator on the dashboard cards. */ .ds-flag { font-size: 1.125rem;