refac/fix: group member user list
This commit is contained in:
@@ -11,21 +11,23 @@
|
||||
import { getUsers } from '$lib/apis/users';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
import { addUserToGroup, removeUserFromGroup } from '$lib/apis/groups';
|
||||
import { WEBUI_API_BASE_URL } from '$lib/constants';
|
||||
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
import Checkbox from '$lib/components/common/Checkbox.svelte';
|
||||
import Badge from '$lib/components/common/Badge.svelte';
|
||||
import Search from '$lib/components/icons/Search.svelte';
|
||||
import Pagination from '$lib/components/common/Pagination.svelte';
|
||||
import { addUserToGroup, removeUserFromGroup } from '$lib/apis/groups';
|
||||
import ChevronDown from '$lib/components/icons/ChevronDown.svelte';
|
||||
import ChevronUp from '$lib/components/icons/ChevronUp.svelte';
|
||||
import { WEBUI_API_BASE_URL } from '$lib/constants';
|
||||
import Spinner from '$lib/components/common/Spinner.svelte';
|
||||
|
||||
export let groupId: string;
|
||||
export let userCount = 0;
|
||||
|
||||
let users = [];
|
||||
let total = 0;
|
||||
let users = null;
|
||||
let total = null;
|
||||
|
||||
let query = '';
|
||||
let orderBy = `group_id:${groupId}`; // default sort key
|
||||
@@ -100,163 +102,169 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if users.length > 0}
|
||||
<div class="scrollbar-hidden relative whitespace-nowrap overflow-x-auto max-w-full">
|
||||
<table
|
||||
class="w-full text-sm text-left text-gray-500 dark:text-gray-400 table-auto max-w-full"
|
||||
>
|
||||
<thead class="text-xs text-gray-800 uppercase bg-transparent dark:text-gray-200">
|
||||
<tr class=" border-b-[1.5px] border-gray-50 dark:border-gray-850">
|
||||
<th
|
||||
scope="col"
|
||||
class="px-2.5 py-2 cursor-pointer text-left w-8"
|
||||
on:click={() => setSortKey(`group_id:${groupId}`)}
|
||||
>
|
||||
<div class="flex gap-1.5 items-center">
|
||||
{$i18n.t('MBR')}
|
||||
|
||||
{#if orderBy === `group_id:${groupId}`}
|
||||
<span class="font-normal"
|
||||
>{#if direction === 'asc'}
|
||||
<ChevronUp className="size-2" />
|
||||
{:else}
|
||||
<ChevronDown className="size-2" />
|
||||
{/if}
|
||||
</span>
|
||||
{:else}
|
||||
<span class="invisible">
|
||||
<ChevronUp className="size-2" />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</th>
|
||||
|
||||
<th
|
||||
scope="col"
|
||||
class="px-2.5 py-2 cursor-pointer select-none"
|
||||
on:click={() => setSortKey('role')}
|
||||
>
|
||||
<div class="flex gap-1.5 items-center">
|
||||
{$i18n.t('Role')}
|
||||
|
||||
{#if orderBy === 'role'}
|
||||
<span class="font-normal"
|
||||
>{#if direction === 'asc'}
|
||||
<ChevronUp className="size-2" />
|
||||
{:else}
|
||||
<ChevronDown className="size-2" />
|
||||
{/if}
|
||||
</span>
|
||||
{:else}
|
||||
<span class="invisible">
|
||||
<ChevronUp className="size-2" />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="px-2.5 py-2 cursor-pointer select-none"
|
||||
on:click={() => setSortKey('name')}
|
||||
>
|
||||
<div class="flex gap-1.5 items-center">
|
||||
{$i18n.t('Name')}
|
||||
|
||||
{#if orderBy === 'name'}
|
||||
<span class="font-normal"
|
||||
>{#if direction === 'asc'}
|
||||
<ChevronUp className="size-2" />
|
||||
{:else}
|
||||
<ChevronDown className="size-2" />
|
||||
{/if}
|
||||
</span>
|
||||
{:else}
|
||||
<span class="invisible">
|
||||
<ChevronUp className="size-2" />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</th>
|
||||
|
||||
<th
|
||||
scope="col"
|
||||
class="px-2.5 py-2 cursor-pointer select-none"
|
||||
on:click={() => setSortKey('last_active_at')}
|
||||
>
|
||||
<div class="flex gap-1.5 items-center">
|
||||
{$i18n.t('Last Active')}
|
||||
|
||||
{#if orderBy === 'last_active_at'}
|
||||
<span class="font-normal"
|
||||
>{#if direction === 'asc'}
|
||||
<ChevronUp className="size-2" />
|
||||
{:else}
|
||||
<ChevronDown className="size-2" />
|
||||
{/if}
|
||||
</span>
|
||||
{:else}
|
||||
<span class="invisible">
|
||||
<ChevronUp className="size-2" />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="">
|
||||
{#each users as user, userIdx}
|
||||
<tr class="bg-white dark:bg-gray-900 dark:border-gray-850 text-xs">
|
||||
<td class=" px-3 py-1 w-8">
|
||||
<div class="flex w-full justify-center">
|
||||
<Checkbox
|
||||
state={(user?.group_ids ?? []).includes(groupId) ? 'checked' : 'unchecked'}
|
||||
on:change={(e) => {
|
||||
toggleMember(user.id, e.detail);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-3 py-1 min-w-[7rem] w-28">
|
||||
<div class=" translate-y-0.5">
|
||||
<Badge
|
||||
type={user.role === 'admin'
|
||||
? 'info'
|
||||
: user.role === 'user'
|
||||
? 'success'
|
||||
: 'muted'}
|
||||
content={$i18n.t(user.role)}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-3 py-1 font-medium text-gray-900 dark:text-white max-w-48">
|
||||
<Tooltip content={user.email} placement="top-start">
|
||||
<div class="flex items-center">
|
||||
<img
|
||||
class="rounded-full w-6 h-6 object-cover mr-2.5 flex-shrink-0"
|
||||
src={`${WEBUI_API_BASE_URL}/users/${user.id}/profile/image`}
|
||||
alt="user"
|
||||
/>
|
||||
|
||||
<div class="font-medium truncate">{user.name}</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</td>
|
||||
|
||||
<td class=" px-3 py-1">
|
||||
{dayjs(user.last_active_at * 1000).fromNow()}
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
{#if users === null || total === null}
|
||||
<div class="my-10">
|
||||
<Spinner className="size-5" />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-gray-500 text-xs text-center py-2 px-10">
|
||||
{$i18n.t('No users were found.')}
|
||||
</div>
|
||||
{/if}
|
||||
{#if users.length > 0}
|
||||
<div class="scrollbar-hidden relative whitespace-nowrap overflow-x-auto max-w-full">
|
||||
<table
|
||||
class="w-full text-sm text-left text-gray-500 dark:text-gray-400 table-auto max-w-full"
|
||||
>
|
||||
<thead class="text-xs text-gray-800 uppercase bg-transparent dark:text-gray-200">
|
||||
<tr class=" border-b-[1.5px] border-gray-50 dark:border-gray-850">
|
||||
<th
|
||||
scope="col"
|
||||
class="px-2.5 py-2 cursor-pointer text-left w-8"
|
||||
on:click={() => setSortKey(`group_id:${groupId}`)}
|
||||
>
|
||||
<div class="flex gap-1.5 items-center">
|
||||
{$i18n.t('MBR')}
|
||||
|
||||
{#if total > 30}
|
||||
<Pagination bind:page count={total} perPage={30} />
|
||||
{#if orderBy === `group_id:${groupId}`}
|
||||
<span class="font-normal"
|
||||
>{#if direction === 'asc'}
|
||||
<ChevronUp className="size-2" />
|
||||
{:else}
|
||||
<ChevronDown className="size-2" />
|
||||
{/if}
|
||||
</span>
|
||||
{:else}
|
||||
<span class="invisible">
|
||||
<ChevronUp className="size-2" />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</th>
|
||||
|
||||
<th
|
||||
scope="col"
|
||||
class="px-2.5 py-2 cursor-pointer select-none"
|
||||
on:click={() => setSortKey('role')}
|
||||
>
|
||||
<div class="flex gap-1.5 items-center">
|
||||
{$i18n.t('Role')}
|
||||
|
||||
{#if orderBy === 'role'}
|
||||
<span class="font-normal"
|
||||
>{#if direction === 'asc'}
|
||||
<ChevronUp className="size-2" />
|
||||
{:else}
|
||||
<ChevronDown className="size-2" />
|
||||
{/if}
|
||||
</span>
|
||||
{:else}
|
||||
<span class="invisible">
|
||||
<ChevronUp className="size-2" />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="px-2.5 py-2 cursor-pointer select-none"
|
||||
on:click={() => setSortKey('name')}
|
||||
>
|
||||
<div class="flex gap-1.5 items-center">
|
||||
{$i18n.t('Name')}
|
||||
|
||||
{#if orderBy === 'name'}
|
||||
<span class="font-normal"
|
||||
>{#if direction === 'asc'}
|
||||
<ChevronUp className="size-2" />
|
||||
{:else}
|
||||
<ChevronDown className="size-2" />
|
||||
{/if}
|
||||
</span>
|
||||
{:else}
|
||||
<span class="invisible">
|
||||
<ChevronUp className="size-2" />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</th>
|
||||
|
||||
<th
|
||||
scope="col"
|
||||
class="px-2.5 py-2 cursor-pointer select-none"
|
||||
on:click={() => setSortKey('last_active_at')}
|
||||
>
|
||||
<div class="flex gap-1.5 items-center">
|
||||
{$i18n.t('Last Active')}
|
||||
|
||||
{#if orderBy === 'last_active_at'}
|
||||
<span class="font-normal"
|
||||
>{#if direction === 'asc'}
|
||||
<ChevronUp className="size-2" />
|
||||
{:else}
|
||||
<ChevronDown className="size-2" />
|
||||
{/if}
|
||||
</span>
|
||||
{:else}
|
||||
<span class="invisible">
|
||||
<ChevronUp className="size-2" />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="">
|
||||
{#each users as user, userIdx (user?.id ?? userIdx)}
|
||||
<tr class="bg-white dark:bg-gray-900 dark:border-gray-850 text-xs">
|
||||
<td class=" px-3 py-1 w-8">
|
||||
<div class="flex w-full justify-center">
|
||||
<Checkbox
|
||||
state={(user?.group_ids ?? []).includes(groupId) ? 'checked' : 'unchecked'}
|
||||
on:change={(e) => {
|
||||
toggleMember(user.id, e.detail);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-3 py-1 min-w-[7rem] w-28">
|
||||
<div class=" translate-y-0.5">
|
||||
<Badge
|
||||
type={user.role === 'admin'
|
||||
? 'info'
|
||||
: user.role === 'user'
|
||||
? 'success'
|
||||
: 'muted'}
|
||||
content={$i18n.t(user.role)}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-3 py-1 font-medium text-gray-900 dark:text-white max-w-48">
|
||||
<Tooltip content={user.email} placement="top-start">
|
||||
<div class="flex items-center">
|
||||
<img
|
||||
class="rounded-full w-6 h-6 object-cover mr-2.5 flex-shrink-0"
|
||||
src={`${WEBUI_API_BASE_URL}/users/${user.id}/profile/image`}
|
||||
alt="user"
|
||||
/>
|
||||
|
||||
<div class="font-medium truncate">{user.name}</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</td>
|
||||
|
||||
<td class=" px-3 py-1">
|
||||
{dayjs(user.last_active_at * 1000).fromNow()}
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-gray-500 text-xs text-center py-2 px-10">
|
||||
{$i18n.t('No users were found.')}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if total > 30}
|
||||
<Pagination bind:page count={total} perPage={30} />
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user