forked from open-webui/open-webui
		
	feat: admin panel added
This commit is contained in:
		
							parent
							
								
									8547b7807d
								
							
						
					
					
						commit
						07d2c9871f
					
				
					 9 changed files with 326 additions and 1087 deletions
				
			
		|  | @ -389,31 +389,33 @@ | |||
| 					<div class=" self-center">Add-ons</div> | ||||
| 				</button> | ||||
| 
 | ||||
| 				<button | ||||
| 					class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab === | ||||
| 					'auth' | ||||
| 						? 'bg-gray-200 dark:bg-gray-700' | ||||
| 						: ' hover:bg-gray-300 dark:hover:bg-gray-800'}" | ||||
| 					on:click={() => { | ||||
| 						selectedTab = 'auth'; | ||||
| 					}} | ||||
| 				> | ||||
| 					<div class=" self-center mr-2"> | ||||
| 						<svg | ||||
| 							xmlns="http://www.w3.org/2000/svg" | ||||
| 							viewBox="0 0 24 24" | ||||
| 							fill="currentColor" | ||||
| 							class="w-4 h-4" | ||||
| 						> | ||||
| 							<path | ||||
| 								fill-rule="evenodd" | ||||
| 								d="M12.516 2.17a.75.75 0 00-1.032 0 11.209 11.209 0 01-7.877 3.08.75.75 0 00-.722.515A12.74 12.74 0 002.25 9.75c0 5.942 4.064 10.933 9.563 12.348a.749.749 0 00.374 0c5.499-1.415 9.563-6.406 9.563-12.348 0-1.39-.223-2.73-.635-3.985a.75.75 0 00-.722-.516l-.143.001c-2.996 0-5.717-1.17-7.734-3.08zm3.094 8.016a.75.75 0 10-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 00-1.06 1.06l2.25 2.25a.75.75 0 001.14-.094l3.75-5.25z" | ||||
| 								clip-rule="evenodd" | ||||
| 							/> | ||||
| 						</svg> | ||||
| 					</div> | ||||
| 					<div class=" self-center">Authentication</div> | ||||
| 				</button> | ||||
| 				{#if !$config || ($config && !$config.auth)} | ||||
| 					<button | ||||
| 						class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab === | ||||
| 						'auth' | ||||
| 							? 'bg-gray-200 dark:bg-gray-700' | ||||
| 							: ' hover:bg-gray-300 dark:hover:bg-gray-800'}" | ||||
| 						on:click={() => { | ||||
| 							selectedTab = 'auth'; | ||||
| 						}} | ||||
| 					> | ||||
| 						<div class=" self-center mr-2"> | ||||
| 							<svg | ||||
| 								xmlns="http://www.w3.org/2000/svg" | ||||
| 								viewBox="0 0 24 24" | ||||
| 								fill="currentColor" | ||||
| 								class="w-4 h-4" | ||||
| 							> | ||||
| 								<path | ||||
| 									fill-rule="evenodd" | ||||
| 									d="M12.516 2.17a.75.75 0 00-1.032 0 11.209 11.209 0 01-7.877 3.08.75.75 0 00-.722.515A12.74 12.74 0 002.25 9.75c0 5.942 4.064 10.933 9.563 12.348a.749.749 0 00.374 0c5.499-1.415 9.563-6.406 9.563-12.348 0-1.39-.223-2.73-.635-3.985a.75.75 0 00-.722-.516l-.143.001c-2.996 0-5.717-1.17-7.734-3.08zm3.094 8.016a.75.75 0 10-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 00-1.06 1.06l2.25 2.25a.75.75 0 001.14-.094l3.75-5.25z" | ||||
| 									clip-rule="evenodd" | ||||
| 								/> | ||||
| 							</svg> | ||||
| 						</div> | ||||
| 						<div class=" self-center">Authentication</div> | ||||
| 					</button> | ||||
| 				{/if} | ||||
| 
 | ||||
| 				<button | ||||
| 					class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab === | ||||
|  |  | |||
|  | @ -398,7 +398,11 @@ | |||
| 						}} | ||||
| 					> | ||||
| 						<div class=" self-center mr-3"> | ||||
| 							<img src={$user.profile_image_url} class=" max-w-[30px] object-cover rounded-full" /> | ||||
| 							<img | ||||
| 								src={$user.profile_image_url} | ||||
| 								class=" max-w-[30px] object-cover rounded-full" | ||||
| 								alt="User profile" | ||||
| 							/> | ||||
| 						</div> | ||||
| 						<div class=" self-center font-semibold">{$user.name}</div> | ||||
| 					</button> | ||||
|  | @ -409,6 +413,33 @@ | |||
| 							class="absolute z-10 bottom-[70px] 4.5rem rounded-lg shadow w-[240px] bg-gray-900" | ||||
| 						> | ||||
| 							<div class="py-2 w-full"> | ||||
| 								{#if $user.role === 'admin'} | ||||
| 									<button | ||||
| 										class="flex py-2.5 px-3.5 w-full hover:bg-gray-800 transition" | ||||
| 										on:click={() => { | ||||
| 											goto('/admin'); | ||||
| 										}} | ||||
| 									> | ||||
| 										<div class=" self-center mr-3"> | ||||
| 											<svg | ||||
| 												xmlns="http://www.w3.org/2000/svg" | ||||
| 												fill="none" | ||||
| 												viewBox="0 0 24 24" | ||||
| 												stroke-width="1.5" | ||||
| 												stroke="currentColor" | ||||
| 												class="w-5 h-5" | ||||
| 											> | ||||
| 												<path | ||||
| 													stroke-linecap="round" | ||||
| 													stroke-linejoin="round" | ||||
| 													d="M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z" | ||||
| 												/> | ||||
| 											</svg> | ||||
| 										</div> | ||||
| 										<div class=" self-center font-medium">Admin Panel</div> | ||||
| 									</button> | ||||
| 								{/if} | ||||
| 
 | ||||
| 								<button | ||||
| 									class="flex py-2.5 px-3.5 w-full hover:bg-gray-800 transition" | ||||
| 									on:click={() => { | ||||
|  |  | |||
							
								
								
									
										154
									
								
								src/routes/admin/+page.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								src/routes/admin/+page.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,154 @@ | |||
| <script> | ||||
| 	import { WEBUI_API_BASE_URL } from '$lib/constants'; | ||||
| 	import { config, user } from '$lib/stores'; | ||||
| 	import { goto } from '$app/navigation'; | ||||
| 	import { onMount } from 'svelte'; | ||||
| 
 | ||||
| 	import toast from 'svelte-french-toast'; | ||||
| 
 | ||||
| 	let loaded = false; | ||||
| 	let users = []; | ||||
| 
 | ||||
| 	const updateUserRole = async (id, role) => { | ||||
| 		const res = await fetch(`${WEBUI_API_BASE_URL}/users/update/role`, { | ||||
| 			method: 'POST', | ||||
| 			headers: { | ||||
| 				'Content-Type': 'application/json', | ||||
| 				Authorization: `Bearer ${localStorage.token}` | ||||
| 			}, | ||||
| 			body: JSON.stringify({ | ||||
| 				id: id, | ||||
| 				role: role | ||||
| 			}) | ||||
| 		}) | ||||
| 			.then(async (res) => { | ||||
| 				if (!res.ok) throw await res.json(); | ||||
| 				return res.json(); | ||||
| 			}) | ||||
| 			.catch((error) => { | ||||
| 				console.log(error); | ||||
| 				toast.error(error.detail); | ||||
| 				return null; | ||||
| 			}); | ||||
| 
 | ||||
| 		if (res) { | ||||
| 			await getUsers(); | ||||
| 		} | ||||
| 	}; | ||||
| 
 | ||||
| 	const getUsers = async () => { | ||||
| 		const res = await fetch(`${WEBUI_API_BASE_URL}/users/`, { | ||||
| 			method: 'GET', | ||||
| 			headers: { | ||||
| 				'Content-Type': 'application/json', | ||||
| 				Authorization: `Bearer ${localStorage.token}` | ||||
| 			} | ||||
| 		}) | ||||
| 			.then(async (res) => { | ||||
| 				if (!res.ok) throw await res.json(); | ||||
| 				return res.json(); | ||||
| 			}) | ||||
| 			.catch((error) => { | ||||
| 				console.log(error); | ||||
| 				toast.error(error.detail); | ||||
| 				return null; | ||||
| 			}); | ||||
| 
 | ||||
| 		users = res ? res : []; | ||||
| 	}; | ||||
| 
 | ||||
| 	onMount(async () => { | ||||
| 		if ($config === null || !$config.auth || ($config.auth && $user && $user.role !== 'admin')) { | ||||
| 			await goto('/'); | ||||
| 		} else { | ||||
| 			await getUsers(); | ||||
| 		} | ||||
| 		loaded = true; | ||||
| 	}); | ||||
| </script> | ||||
| 
 | ||||
| <div | ||||
| 	class=" bg-white dark:bg-gray-800 dark:text-gray-100 min-h-screen w-full flex justify-center font-mona" | ||||
| > | ||||
| 	{#if loaded} | ||||
| 		<div class="w-full max-w-3xl px-10 md:px-16 min-h-screen flex flex-col"> | ||||
| 			<div class="py-10 w-full"> | ||||
| 				<div class=" flex flex-col justify-center"> | ||||
| 					<div class=" text-2xl font-semibold">Users ({users.length})</div> | ||||
| 					<div class=" text-gray-500 text-xs font-medium mt-1"> | ||||
| 						Click on the user role cell in the table to change a user's role. | ||||
| 					</div> | ||||
| 
 | ||||
| 					<hr class=" my-3 dark:border-gray-600" /> | ||||
| 
 | ||||
| 					<div class="scrollbar-hidden relative overflow-x-auto whitespace-nowrap"> | ||||
| 						<table class="w-full text-sm text-left text-gray-500 dark:text-gray-400"> | ||||
| 							<thead | ||||
| 								class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400" | ||||
| 							> | ||||
| 								<tr> | ||||
| 									<th scope="col" class="px-6 py-3"> Name </th> | ||||
| 									<th scope="col" class="px-6 py-3"> Email </th> | ||||
| 									<th scope="col" class="px-6 py-3"> Role </th> | ||||
| 									<!-- <th scope="col" class="px-6 py-3"> Action </th> --> | ||||
| 								</tr> | ||||
| 							</thead> | ||||
| 							<tbody> | ||||
| 								{#each users as user} | ||||
| 									<tr class="bg-white border-b dark:bg-gray-800 dark:border-gray-700"> | ||||
| 										<th | ||||
| 											scope="row" | ||||
| 											class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white" | ||||
| 										> | ||||
| 											<div class="flex flex-row"> | ||||
| 												<img | ||||
| 													class=" rounded-full max-w-[30px] max-h-[30px] object-cover mr-4" | ||||
| 													src={user.profile_image_url} | ||||
| 												/> | ||||
| 
 | ||||
| 												<div class=" font-semibold md:self-center">{user.name}</div> | ||||
| 											</div> | ||||
| 										</th> | ||||
| 										<td class="px-6 py-4"> {user.email} </td> | ||||
| 										<td class="px-6 py-4"> | ||||
| 											<button | ||||
| 												class="  text-white underline" | ||||
| 												on:click={() => { | ||||
| 													if (user.role === 'user') { | ||||
| 														updateUserRole(user.id, 'admin'); | ||||
| 													} else if (user.role === 'pending') { | ||||
| 														updateUserRole(user.id, 'user'); | ||||
| 													} else { | ||||
| 														updateUserRole(user.id, 'pending'); | ||||
| 													} | ||||
| 												}}>{user.role}</button | ||||
| 											> | ||||
| 										</td> | ||||
| 										<!-- <td class="px-6 py-4 text-center"> | ||||
| 											<button class="  text-white underline"> Edit </button> | ||||
| 										</td> --> | ||||
| 									</tr> | ||||
| 								{/each} | ||||
| 							</tbody> | ||||
| 						</table> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	{/if} | ||||
| </div> | ||||
| 
 | ||||
| <style> | ||||
| 	.font-mona { | ||||
| 		font-family: 'Mona Sans'; | ||||
| 	} | ||||
| 
 | ||||
| 	.scrollbar-hidden::-webkit-scrollbar { | ||||
| 		display: none; /* for Chrome, Safari and Opera */ | ||||
| 	} | ||||
| 
 | ||||
| 	.scrollbar-hidden { | ||||
| 		-ms-overflow-style: none; /* IE and Edge */ | ||||
| 		scrollbar-width: none; /* Firefox */ | ||||
| 	} | ||||
| </style> | ||||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Timothy J. Baek
						Timothy J. Baek