diff --git a/web/src/lib/api.ts b/web/src/lib/api.ts index 4eaa671..f118ef4 100644 --- a/web/src/lib/api.ts +++ b/web/src/lib/api.ts @@ -168,6 +168,22 @@ export const api = { createUser: (username: string, password: string, display_name: string, is_admin: boolean) => request<{ user: User }>('POST', '/api/v1/users', { username, password, display_name, is_admin }), + + userGrants: (userId: number) => + request<{ grants: { library_id: number; can_read: boolean; can_download: boolean }[] }>( + 'GET', + `/api/v1/users/${userId}/grants`, + ), + + putGrant: (userId: number, libraryId: number, canRead: boolean, canDownload: boolean) => + request<{ user_id: number; library_id: number; can_read: boolean; can_download: boolean }>( + 'PUT', + `/api/v1/users/${userId}/grants/${libraryId}`, + { can_read: canRead, can_download: canDownload }, + ), + + deleteGrant: (userId: number, libraryId: number) => + request('DELETE', `/api/v1/users/${userId}/grants/${libraryId}`), } export interface SearchResult { diff --git a/web/src/lib/components/Users.svelte b/web/src/lib/components/Users.svelte index 9cb452a..8cb2ed4 100644 --- a/web/src/lib/components/Users.svelte +++ b/web/src/lib/components/Users.svelte @@ -1,8 +1,9 @@ {#if error}
{error}
{/if} @@ -50,12 +95,53 @@
{:else} {#each users as u (u.id)} -
-
- {u.display_name || u.username} - {u.username} +
+
+
+ {u.display_name || u.username} + {u.username} +
+ {#if u.is_admin}admin{/if} +
- {#if u.is_admin}admin{/if} + + {#if expanded === u.id} +
+ {#if u.is_admin} +

Administrators can read every library.

+ {:else if loadingGrants} +
+ {:else if libraries.length === 0} +

No libraries exist yet.

+ {:else} + {#each libraries as lib (lib.id)} +
+ {lib.name} {lib.kind} + + +
+ {/each} + {/if} +
+ {/if}
{/each} {/if} @@ -82,7 +168,6 @@ -

Grant library access per user from the API; a grants UI is coming.

@@ -101,24 +186,50 @@ place-items: center; padding: var(--space-5); } + .userblock { + border-bottom: 1px solid var(--border); + } + .userblock:last-child { + border-bottom: none; + } .row { display: flex; align-items: center; - justify-content: space-between; gap: var(--space-3); padding: var(--space-3) var(--space-4); - border-bottom: 1px solid var(--border); - } - .row:last-child { - border-bottom: none; } .id { display: flex; flex-direction: column; + flex: 1; } .id .muted { font-size: 12px; } + .grants { + padding: 0 var(--space-4) var(--space-4); + display: flex; + flex-direction: column; + gap: var(--space-2); + } + .note { + margin: 0; + font-size: 12px; + } + .grantrow { + display: flex; + align-items: center; + gap: var(--space-3); + } + .libname { + flex: 1; + font-size: 13px; + } + .libname .faint { + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.04em; + } .form { display: flex; flex-direction: column; @@ -128,12 +239,9 @@ .check { display: flex; align-items: center; - gap: var(--space-2); + gap: 6px; font-size: 13px; color: var(--text-muted); - } - .hint { - font-size: 12px; - margin: 0; + white-space: nowrap; }