Groups.svelte 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. <script>
  2. import { toast } from 'svelte-sonner';
  3. import dayjs from 'dayjs';
  4. import relativeTime from 'dayjs/plugin/relativeTime';
  5. dayjs.extend(relativeTime);
  6. import { onMount, getContext } from 'svelte';
  7. import { goto } from '$app/navigation';
  8. import { WEBUI_NAME, config, user, showSidebar, knowledge } from '$lib/stores';
  9. import { WEBUI_BASE_URL } from '$lib/constants';
  10. import Tooltip from '$lib/components/common/Tooltip.svelte';
  11. import Plus from '$lib/components/icons/Plus.svelte';
  12. import Badge from '$lib/components/common/Badge.svelte';
  13. import UsersSolid from '$lib/components/icons/UsersSolid.svelte';
  14. import ChevronRight from '$lib/components/icons/ChevronRight.svelte';
  15. import EllipsisHorizontal from '$lib/components/icons/EllipsisHorizontal.svelte';
  16. import User from '$lib/components/icons/User.svelte';
  17. import UserCircleSolid from '$lib/components/icons/UserCircleSolid.svelte';
  18. import GroupModal from './Groups/EditGroupModal.svelte';
  19. import Pencil from '$lib/components/icons/Pencil.svelte';
  20. import GroupItem from './Groups/GroupItem.svelte';
  21. import AddGroupModal from './Groups/AddGroupModal.svelte';
  22. import { createNewGroup, getGroups } from '$lib/apis/groups';
  23. const i18n = getContext('i18n');
  24. let loaded = false;
  25. export let users = [];
  26. let groups = [];
  27. let filteredGroups;
  28. $: filteredGroups = groups.filter((user) => {
  29. if (search === '') {
  30. return true;
  31. } else {
  32. let name = user.name.toLowerCase();
  33. const query = search.toLowerCase();
  34. return name.includes(query);
  35. }
  36. });
  37. let search = '';
  38. let showCreateGroupModal = false;
  39. let showDefaultPermissionsModal = false;
  40. const setGroups = async () => {
  41. groups = await getGroups(localStorage.token);
  42. };
  43. const addGroupHandler = async (group) => {
  44. const res = await createNewGroup(localStorage.token, group).catch((error) => {
  45. toast.error(error);
  46. return null;
  47. });
  48. if (res) {
  49. toast.success($i18n.t('Group created successfully'));
  50. groups = await getGroups(localStorage.token);
  51. }
  52. };
  53. const updateDefaultPermissionsHandler = async (permissions) => {
  54. console.log(permissions);
  55. };
  56. onMount(async () => {
  57. if ($user?.role !== 'admin') {
  58. await goto('/');
  59. } else {
  60. await setGroups();
  61. }
  62. loaded = true;
  63. });
  64. </script>
  65. {#if loaded}
  66. <AddGroupModal bind:show={showCreateGroupModal} onSubmit={addGroupHandler} />
  67. <div class="mt-0.5 mb-2 gap-1 flex flex-col md:flex-row justify-between">
  68. <div class="flex md:self-center text-lg font-medium px-0.5">
  69. {$i18n.t('Groups')}
  70. <div class="flex self-center w-[1px] h-6 mx-2.5 bg-gray-50 dark:bg-gray-850" />
  71. <span class="text-lg font-medium text-gray-500 dark:text-gray-300">{groups.length}</span>
  72. </div>
  73. <div class="flex gap-1">
  74. <div class=" flex w-full space-x-2">
  75. <div class="flex flex-1">
  76. <div class=" self-center ml-1 mr-3">
  77. <svg
  78. xmlns="http://www.w3.org/2000/svg"
  79. viewBox="0 0 20 20"
  80. fill="currentColor"
  81. class="w-4 h-4"
  82. >
  83. <path
  84. fill-rule="evenodd"
  85. d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z"
  86. clip-rule="evenodd"
  87. />
  88. </svg>
  89. </div>
  90. <input
  91. class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-none bg-transparent"
  92. bind:value={search}
  93. placeholder={$i18n.t('Search')}
  94. />
  95. </div>
  96. <div>
  97. <Tooltip content={$i18n.t('Create Group')}>
  98. <button
  99. class=" p-2 rounded-xl hover:bg-gray-100 dark:bg-gray-900 dark:hover:bg-gray-850 transition font-medium text-sm flex items-center space-x-1"
  100. on:click={() => {
  101. showCreateGroupModal = !showCreateGroupModal;
  102. }}
  103. >
  104. <Plus className="size-3.5" />
  105. </button>
  106. </Tooltip>
  107. </div>
  108. </div>
  109. </div>
  110. </div>
  111. <div>
  112. {#if filteredGroups.length === 0}
  113. <div class="flex flex-col items-center justify-center h-40">
  114. <div class=" text-xl font-medium">
  115. {$i18n.t('Organize your users')}
  116. </div>
  117. <div class="mt-1 text-sm dark:text-gray-300">
  118. {$i18n.t('Use groups to group your users and assign permissions.')}
  119. </div>
  120. <div class="mt-3">
  121. <button
  122. class=" px-4 py-1.5 text-sm rounded-full bg-black hover:bg-gray-800 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition font-medium flex items-center space-x-1"
  123. aria-label={$i18n.t('Create Group')}
  124. on:click={() => {
  125. showCreateGroupModal = true;
  126. }}
  127. >
  128. {$i18n.t('Create Group')}
  129. </button>
  130. </div>
  131. </div>
  132. {:else}
  133. <div>
  134. <div class=" flex items-center gap-3 justify-between text-xs uppercase px-1 font-bold">
  135. <div class="w-full">Group</div>
  136. <div class="w-full">Users</div>
  137. <div class="w-full"></div>
  138. </div>
  139. <hr class="mt-1.5 border-gray-50 dark:border-gray-850" />
  140. {#each filteredGroups as group}
  141. <div class="my-2">
  142. <GroupItem {group} {users} {setGroups} />
  143. </div>
  144. {/each}
  145. </div>
  146. {/if}
  147. <hr class="mb-2 border-gray-50 dark:border-gray-850" />
  148. <GroupModal
  149. bind:show={showDefaultPermissionsModal}
  150. tabs={['permissions']}
  151. custom={false}
  152. onSubmit={updateDefaultPermissionsHandler}
  153. />
  154. <button
  155. class="flex items-center justify-between rounded-lg w-full transition pt-1"
  156. on:click={() => {
  157. showDefaultPermissionsModal = true;
  158. }}
  159. >
  160. <div class="flex items-center gap-2.5">
  161. <div class="p-1.5 bg-black/5 dark:bg-white/10 rounded-full">
  162. <UsersSolid className="size-4" />
  163. </div>
  164. <div class="text-left">
  165. <div class=" text-sm font-medium">{$i18n.t('Default permissions')}</div>
  166. <div class="flex text-xs mt-0.5">
  167. {$i18n.t('applies to all users with the "user" role')}
  168. </div>
  169. </div>
  170. </div>
  171. <div>
  172. <ChevronRight strokeWidth="2.5" />
  173. </div>
  174. </button>
  175. </div>
  176. {/if}