forked from open-webui/open-webui
		
	refac: styling
This commit is contained in:
		
							parent
							
								
									ebc6a269d3
								
							
						
					
					
						commit
						b7fcf14f6e
					
				
					 13 changed files with 259 additions and 33 deletions
				
			
		|  | @ -89,6 +89,10 @@ class SignupForm(BaseModel): | |||
|     profile_image_url: Optional[str] = "/user.png" | ||||
| 
 | ||||
| 
 | ||||
| class AddUserForm(SignupForm): | ||||
|     role: str = "pending" | ||||
| 
 | ||||
| 
 | ||||
| class AuthsTable: | ||||
|     def __init__(self, db): | ||||
|         self.db = db | ||||
|  |  | |||
|  | @ -11,6 +11,7 @@ import uuid | |||
| from apps.web.models.auths import ( | ||||
|     SigninForm, | ||||
|     SignupForm, | ||||
|     AddUserForm, | ||||
|     UpdateProfileForm, | ||||
|     UpdatePasswordForm, | ||||
|     UserResponse, | ||||
|  | @ -205,6 +206,50 @@ async def signup(request: Request, form_data: SignupForm): | |||
|         raise HTTPException(500, detail=ERROR_MESSAGES.DEFAULT(err)) | ||||
| 
 | ||||
| 
 | ||||
| ############################ | ||||
| # AddUser | ||||
| ############################ | ||||
| 
 | ||||
| 
 | ||||
| @router.post("/add", response_model=SigninResponse) | ||||
| async def signup(form_data: AddUserForm, user=Depends(get_admin_user)): | ||||
| 
 | ||||
|     if not validate_email_format(form_data.email.lower()): | ||||
|         raise HTTPException( | ||||
|             status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.INVALID_EMAIL_FORMAT | ||||
|         ) | ||||
| 
 | ||||
|     if Users.get_user_by_email(form_data.email.lower()): | ||||
|         raise HTTPException(400, detail=ERROR_MESSAGES.EMAIL_TAKEN) | ||||
| 
 | ||||
|     try: | ||||
|         role = form_data.role | ||||
|         hashed = get_password_hash(form_data.password) | ||||
|         user = Auths.insert_new_auth( | ||||
|             form_data.email.lower(), | ||||
|             hashed, | ||||
|             form_data.name, | ||||
|             form_data.profile_image_url, | ||||
|             role, | ||||
|         ) | ||||
| 
 | ||||
|         if user: | ||||
|             token = create_token(data={"id": user.id}) | ||||
|             return { | ||||
|                 "token": token, | ||||
|                 "token_type": "Bearer", | ||||
|                 "id": user.id, | ||||
|                 "email": user.email, | ||||
|                 "name": user.name, | ||||
|                 "role": user.role, | ||||
|                 "profile_image_url": user.profile_image_url, | ||||
|             } | ||||
|         else: | ||||
|             raise HTTPException(500, detail=ERROR_MESSAGES.CREATE_USER_ERROR) | ||||
|     except Exception as err: | ||||
|         raise HTTPException(500, detail=ERROR_MESSAGES.DEFAULT(err)) | ||||
| 
 | ||||
| 
 | ||||
| ############################ | ||||
| # ToggleSignUp | ||||
| ############################ | ||||
|  |  | |||
|  | @ -95,6 +95,44 @@ export const userSignUp = async ( | |||
| 	return res; | ||||
| }; | ||||
| 
 | ||||
| export const addUser = async ( | ||||
| 	token: string, | ||||
| 	name: string, | ||||
| 	email: string, | ||||
| 	password: string, | ||||
| 	role: string | ||||
| ) => { | ||||
| 	let error = null; | ||||
| 
 | ||||
| 	const res = await fetch(`${WEBUI_API_BASE_URL}/auths/add`, { | ||||
| 		method: 'POST', | ||||
| 		headers: { | ||||
| 			'Content-Type': 'application/json' | ||||
| 		}, | ||||
| 		body: JSON.stringify({ | ||||
| 			name: name, | ||||
| 			email: email, | ||||
| 			password: password, | ||||
| 			role: role | ||||
| 		}) | ||||
| 	}) | ||||
| 		.then(async (res) => { | ||||
| 			if (!res.ok) throw await res.json(); | ||||
| 			return res.json(); | ||||
| 		}) | ||||
| 		.catch((err) => { | ||||
| 			console.log(err); | ||||
| 			error = err.detail; | ||||
| 			return null; | ||||
| 		}); | ||||
| 
 | ||||
| 	if (error) { | ||||
| 		throw error; | ||||
| 	} | ||||
| 
 | ||||
| 	return res; | ||||
| }; | ||||
| 
 | ||||
| export const updateUserProfile = async (token: string, name: string, profileImageUrl: string) => { | ||||
| 	let error = null; | ||||
| 
 | ||||
|  |  | |||
|  | @ -0,0 +1,152 @@ | |||
| <script lang="ts"> | ||||
| 	import { toast } from 'svelte-sonner'; | ||||
| 	import { createEventDispatcher } from 'svelte'; | ||||
| 	import { onMount, getContext } from 'svelte'; | ||||
| 	import { addUser } from '$lib/apis/auths'; | ||||
| 
 | ||||
| 	import Modal from '../common/Modal.svelte'; | ||||
| 
 | ||||
| 	const i18n = getContext('i18n'); | ||||
| 	const dispatch = createEventDispatcher(); | ||||
| 
 | ||||
| 	export let show = false; | ||||
| 
 | ||||
| 	let _user = { | ||||
| 		name: '', | ||||
| 		email: '', | ||||
| 		password: '', | ||||
| 		role: '' | ||||
| 	}; | ||||
| 
 | ||||
| 	const submitHandler = async () => { | ||||
| 		const res = await addUser( | ||||
| 			localStorage.token, | ||||
| 			_user.name, | ||||
| 			_user.email, | ||||
| 			_user.password, | ||||
| 			_user.role | ||||
| 		).catch((error) => { | ||||
| 			toast.error(error); | ||||
| 		}); | ||||
| 
 | ||||
| 		if (res) { | ||||
| 			dispatch('save'); | ||||
| 			show = false; | ||||
| 		} | ||||
| 	}; | ||||
| </script> | ||||
| 
 | ||||
| <Modal size="sm" bind:show> | ||||
| 	<div> | ||||
| 		<div class=" flex justify-between dark:text-gray-300 px-5 pt-4 pb-3"> | ||||
| 			<div class=" text-lg font-medium self-center">{$i18n.t('Add User')}</div> | ||||
| 			<button | ||||
| 				class="self-center" | ||||
| 				on:click={() => { | ||||
| 					show = false; | ||||
| 				}} | ||||
| 			> | ||||
| 				<svg | ||||
| 					xmlns="http://www.w3.org/2000/svg" | ||||
| 					viewBox="0 0 20 20" | ||||
| 					fill="currentColor" | ||||
| 					class="w-5 h-5" | ||||
| 				> | ||||
| 					<path | ||||
| 						d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" | ||||
| 					/> | ||||
| 				</svg> | ||||
| 			</button> | ||||
| 		</div> | ||||
| 
 | ||||
| 		<div class="flex flex-col md:flex-row w-full px-5 pb-4 md:space-x-4 dark:text-gray-200"> | ||||
| 			<div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6"> | ||||
| 				<form | ||||
| 					class="flex flex-col w-full" | ||||
| 					on:submit|preventDefault={() => { | ||||
| 						submitHandler(); | ||||
| 					}} | ||||
| 				> | ||||
| 					<div class=" "> | ||||
| 						<div class="flex flex-col w-full"> | ||||
| 							<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Name')}</div> | ||||
| 
 | ||||
| 							<div class="flex-1"> | ||||
| 								<input | ||||
| 									class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 disabled:text-gray-500 dark:disabled:text-gray-500 outline-none" | ||||
| 									type="text" | ||||
| 									bind:value={_user.name} | ||||
| 									placeholder={$i18n.t('Enter Your Full Name')} | ||||
| 									autocomplete="off" | ||||
| 									required | ||||
| 								/> | ||||
| 							</div> | ||||
| 						</div> | ||||
| 
 | ||||
| 						<hr class=" dark:border-gray-800 my-3 w-full" /> | ||||
| 
 | ||||
| 						<div class="flex flex-col w-full"> | ||||
| 							<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Email')}</div> | ||||
| 
 | ||||
| 							<div class="flex-1"> | ||||
| 								<input | ||||
| 									class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 disabled:text-gray-500 dark:disabled:text-gray-500 outline-none" | ||||
| 									type="email" | ||||
| 									bind:value={_user.email} | ||||
| 									placeholder={$i18n.t('Enter Your Email')} | ||||
| 									autocomplete="off" | ||||
| 									required | ||||
| 								/> | ||||
| 							</div> | ||||
| 						</div> | ||||
| 
 | ||||
| 						<div class="flex flex-col w-full mt-2"> | ||||
| 							<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Password')}</div> | ||||
| 
 | ||||
| 							<div class="flex-1"> | ||||
| 								<input | ||||
| 									class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 disabled:text-gray-500 dark:disabled:text-gray-500 outline-none" | ||||
| 									type="password" | ||||
| 									bind:value={_user.password} | ||||
| 									placeholder={$i18n.t('Enter Your Password')} | ||||
| 									autocomplete="off" | ||||
| 								/> | ||||
| 							</div> | ||||
| 						</div> | ||||
| 					</div> | ||||
| 
 | ||||
| 					<div class="flex justify-end pt-3 text-sm font-medium"> | ||||
| 						<button | ||||
| 							class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg" | ||||
| 							type="submit" | ||||
| 						> | ||||
| 							{$i18n.t('Submit')} | ||||
| 						</button> | ||||
| 					</div> | ||||
| 				</form> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </Modal> | ||||
| 
 | ||||
| <style> | ||||
| 	input::-webkit-outer-spin-button, | ||||
| 	input::-webkit-inner-spin-button { | ||||
| 		/* display: none; <- Crashes Chrome on hover */ | ||||
| 		-webkit-appearance: none; | ||||
| 		margin: 0; /* <-- Apparently some margin are still there even though it's hidden */ | ||||
| 	} | ||||
| 
 | ||||
| 	.tabs::-webkit-scrollbar { | ||||
| 		display: none; /* for Chrome, Safari and Opera */ | ||||
| 	} | ||||
| 
 | ||||
| 	.tabs { | ||||
| 		-ms-overflow-style: none; /* IE and Edge */ | ||||
| 		scrollbar-width: none; /* Firefox */ | ||||
| 	} | ||||
| 
 | ||||
| 	input[type='number'] { | ||||
| 		-moz-appearance: textfield; /* Firefox */ | ||||
| 	} | ||||
| </style> | ||||
|  | @ -15,7 +15,7 @@ | |||
| 
 | ||||
| <Modal bind:show> | ||||
| 	<div> | ||||
| 		<div class=" flex justify-between dark:text-gray-300 px-5 py-4"> | ||||
| 		<div class=" flex justify-between dark:text-gray-300 px-5 pt-4 pb-2"> | ||||
| 			<div class=" text-lg font-medium self-center">{$i18n.t('Admin Settings')}</div> | ||||
| 			<button | ||||
| 				class="self-center" | ||||
|  | @ -35,7 +35,6 @@ | |||
| 				</svg> | ||||
| 			</button> | ||||
| 		</div> | ||||
| 		<hr class=" dark:border-gray-800" /> | ||||
| 
 | ||||
| 		<div class="flex flex-col md:flex-row w-full p-4 md:space-x-4"> | ||||
| 			<div | ||||
|  |  | |||
|  | @ -36,7 +36,7 @@ | |||
| 
 | ||||
| <Modal bind:show> | ||||
| 	<div> | ||||
| 		<div class=" flex justify-between dark:text-gray-300 px-5 py-4"> | ||||
| 		<div class=" flex justify-between dark:text-gray-300 px-5 pt-4 pb-1"> | ||||
| 			<div class=" text-lg font-medium self-center">{$i18n.t('Settings')}</div> | ||||
| 			<button | ||||
| 				class="self-center" | ||||
|  | @ -56,7 +56,6 @@ | |||
| 				</svg> | ||||
| 			</button> | ||||
| 		</div> | ||||
| 		<hr class=" dark:border-gray-800" /> | ||||
| 
 | ||||
| 		<div class="flex flex-col md:flex-row w-full p-4 md:space-x-4"> | ||||
| 			<div | ||||
|  |  | |||
|  | @ -71,7 +71,7 @@ | |||
| 
 | ||||
| <Modal bind:show size="sm"> | ||||
| 	<div> | ||||
| 		<div class=" flex justify-between dark:text-gray-300 px-5 py-4"> | ||||
| 		<div class=" flex justify-between dark:text-gray-300 px-5 pt-4 pb-0.5"> | ||||
| 			<div class=" text-lg font-medium self-center">{$i18n.t('Share Chat')}</div> | ||||
| 			<button | ||||
| 				class="self-center" | ||||
|  | @ -91,10 +91,9 @@ | |||
| 				</svg> | ||||
| 			</button> | ||||
| 		</div> | ||||
| 		<hr class=" dark:border-gray-800" /> | ||||
| 
 | ||||
| 		{#if chat} | ||||
| 			<div class="px-4 pt-4 pb-5 w-full flex flex-col justify-center"> | ||||
| 			<div class="px-5 pt-4 pb-5 w-full flex flex-col justify-center"> | ||||
| 				<div class=" text-sm dark:text-gray-300 mb-1"> | ||||
| 					{#if chat.share_id} | ||||
| 						<a href="/s/{chat.share_id}" target="_blank" | ||||
|  |  | |||
|  | @ -9,7 +9,7 @@ | |||
| 
 | ||||
| <Modal bind:show> | ||||
| 	<div> | ||||
| 		<div class=" flex justify-between dark:text-gray-300 px-5 py-4"> | ||||
| 		<div class=" flex justify-between dark:text-gray-300 px-5 pt-4"> | ||||
| 			<div class=" text-lg font-medium self-center">{$i18n.t('Keyboard shortcuts')}</div> | ||||
| 			<button | ||||
| 				class="self-center" | ||||
|  | @ -29,7 +29,6 @@ | |||
| 				</svg> | ||||
| 			</button> | ||||
| 		</div> | ||||
| 		<hr class=" dark:border-gray-800" /> | ||||
| 
 | ||||
| 		<div class="flex flex-col md:flex-row w-full p-5 md:space-x-4 dark:text-gray-200"> | ||||
| 			<div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6"> | ||||
|  |  | |||
|  | @ -96,7 +96,7 @@ | |||
| 
 | ||||
| <Modal size="sm" bind:show> | ||||
| 	<div> | ||||
| 		<div class=" flex justify-between dark:text-gray-300 px-5 py-4"> | ||||
| 		<div class=" flex justify-between dark:text-gray-300 px-5 pt-4"> | ||||
| 			<div class=" text-lg font-medium self-center">{$i18n.t('Add Docs')}</div> | ||||
| 			<button | ||||
| 				class="self-center" | ||||
|  | @ -116,8 +116,6 @@ | |||
| 				</svg> | ||||
| 			</button> | ||||
| 		</div> | ||||
| 		<hr class=" dark:border-gray-800" /> | ||||
| 
 | ||||
| 		<div class="flex flex-col md:flex-row w-full px-5 py-4 md:space-x-4 dark:text-gray-200"> | ||||
| 			<div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6"> | ||||
| 				<form | ||||
|  |  | |||
|  | @ -75,7 +75,7 @@ | |||
| 
 | ||||
| <Modal size="sm" bind:show> | ||||
| 	<div> | ||||
| 		<div class=" flex justify-between dark:text-gray-300 px-5 py-4"> | ||||
| 		<div class=" flex justify-between dark:text-gray-300 px-5 pt-4"> | ||||
| 			<div class=" text-lg font-medium self-center">{$i18n.t('Edit Doc')}</div> | ||||
| 			<button | ||||
| 				class="self-center" | ||||
|  | @ -95,8 +95,6 @@ | |||
| 				</svg> | ||||
| 			</button> | ||||
| 		</div> | ||||
| 		<hr class=" dark:border-gray-800" /> | ||||
| 
 | ||||
| 		<div class="flex flex-col md:flex-row w-full px-5 py-4 md:space-x-4 dark:text-gray-200"> | ||||
| 			<div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6"> | ||||
| 				<form | ||||
|  | @ -111,28 +109,18 @@ | |||
| 
 | ||||
| 							<div class="flex flex-1"> | ||||
| 								<div | ||||
| 									class="bg-gray-200 dark:bg-gray-600 font-bold px-3 py-1 border border-r-0 dark:border-gray-600 rounded-l-lg flex items-center" | ||||
| 									class="bg-gray-200 dark:bg-gray-800 font-bold px-3 py-0.5 border border-r-0 dark:border-gray-800 rounded-l-xl flex items-center" | ||||
| 								> | ||||
| 									# | ||||
| 								</div> | ||||
| 								<input | ||||
| 									class="w-full rounded-r-lg py-2.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 disabled:text-gray-500 dark:disabled:text-gray-500 outline-none" | ||||
| 									class="w-full rounded-r-xl py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 disabled:text-gray-500 dark:disabled:text-gray-500 outline-none" | ||||
| 									type="text" | ||||
| 									bind:value={doc.name} | ||||
| 									autocomplete="off" | ||||
| 									required | ||||
| 								/> | ||||
| 							</div> | ||||
| 
 | ||||
| 							<!-- <div class="flex-1"> | ||||
| 								<input | ||||
| 									class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 disabled:text-gray-500 dark:disabled:text-gray-500 outline-none" | ||||
| 									type="text" | ||||
| 									bind:value={doc.name} | ||||
| 									autocomplete="off" | ||||
| 									required | ||||
| 								/> | ||||
| 							</div> --> | ||||
| 						</div> | ||||
| 
 | ||||
| 						<div class="flex flex-col w-full"> | ||||
|  | @ -140,7 +128,7 @@ | |||
| 
 | ||||
| 							<div class="flex-1"> | ||||
| 								<input | ||||
| 									class="w-full rounded-lg py-2.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none" | ||||
| 									class="w-full rounded-xl py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none" | ||||
| 									type="text" | ||||
| 									bind:value={doc.title} | ||||
| 									autocomplete="off" | ||||
|  | @ -150,7 +138,7 @@ | |||
| 						</div> | ||||
| 
 | ||||
| 						<div class="flex flex-col w-full"> | ||||
| 							<div class=" mb-1.5 text-xs text-gray-500">{$i18n.t('Tags')}</div> | ||||
| 							<div class=" mb-2 text-xs text-gray-500">{$i18n.t('Tags')}</div> | ||||
| 
 | ||||
| 							<Tags {tags} addTag={addTagHandler} deleteTag={deleteTagHandler} /> | ||||
| 						</div> | ||||
|  |  | |||
|  | @ -12,7 +12,7 @@ | |||
| 
 | ||||
| <Modal bind:show> | ||||
| 	<div> | ||||
| 		<div class=" flex justify-between dark:text-gray-300 px-5 py-4"> | ||||
| 		<div class=" flex justify-between dark:text-gray-300 px-5 pt-4"> | ||||
| 			<div class=" text-lg font-medium self-center">{$i18n.t('Document Settings')}</div> | ||||
| 			<button | ||||
| 				class="self-center" | ||||
|  | @ -32,7 +32,6 @@ | |||
| 				</svg> | ||||
| 			</button> | ||||
| 		</div> | ||||
| 		<hr class=" dark:border-gray-800" /> | ||||
| 
 | ||||
| 		<div class="flex flex-col md:flex-row w-full p-4 md:space-x-4"> | ||||
| 			<div | ||||
|  |  | |||
|  | @ -42,7 +42,7 @@ | |||
| 
 | ||||
| <Modal size="lg" bind:show> | ||||
| 	<div> | ||||
| 		<div class=" flex justify-between dark:text-gray-300 px-5 py-4"> | ||||
| 		<div class=" flex justify-between dark:text-gray-300 px-5 pt-4"> | ||||
| 			<div class=" text-lg font-medium self-center">{$i18n.t('Archived Chats')}</div> | ||||
| 			<button | ||||
| 				class="self-center" | ||||
|  | @ -62,7 +62,6 @@ | |||
| 				</svg> | ||||
| 			</button> | ||||
| 		</div> | ||||
| 		<hr class=" dark:border-gray-850" /> | ||||
| 
 | ||||
| 		<div class="flex flex-col md:flex-row w-full px-5 py-4 md:space-x-4 dark:text-gray-200"> | ||||
| 			<div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6"> | ||||
|  |  | |||
|  | @ -18,6 +18,7 @@ | |||
| 	import ChatBubbles from '$lib/components/icons/ChatBubbles.svelte'; | ||||
| 	import Tooltip from '$lib/components/common/Tooltip.svelte'; | ||||
| 	import UserChatsModal from '$lib/components/admin/UserChatsModal.svelte'; | ||||
| 	import AddUserModal from '$lib/components/admin/AddUserModal.svelte'; | ||||
| 
 | ||||
| 	const i18n = getContext('i18n'); | ||||
| 
 | ||||
|  | @ -92,6 +93,12 @@ | |||
| 	/> | ||||
| {/key} | ||||
| 
 | ||||
| <AddUserModal | ||||
| 	bind:show={showAddUserModal} | ||||
| 	on:save={async () => { | ||||
| 		users = await getUsers(localStorage.token); | ||||
| 	}} | ||||
| /> | ||||
| <UserChatsModal bind:show={showUserChatsModal} user={selectedUser} /> | ||||
| <SettingsModal bind:show={showSettingsModal} /> | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Timothy J. Baek
						Timothy J. Baek