Bladeren bron

feat: admin settings

Timothy J. Baek 1 jaar geleden
bovenliggende
commit
511e939b5d

+ 12 - 1
backend/apps/web/main.py

@@ -11,7 +11,15 @@ from apps.web.routers import (
     configs,
     configs,
     utils,
     utils,
 )
 )
-from config import WEBUI_VERSION, WEBUI_AUTH, DEFAULT_MODELS, DEFAULT_PROMPT_SUGGESTIONS, ENABLE_SIGNUP
+from config import (
+    WEBUI_VERSION,
+    WEBUI_AUTH,
+    DEFAULT_MODELS,
+    DEFAULT_PROMPT_SUGGESTIONS,
+    DEFAULT_USER_ROLE,
+    ENABLE_SIGNUP,
+    USER_PERMISSIONS,
+)
 
 
 app = FastAPI()
 app = FastAPI()
 
 
@@ -20,6 +28,9 @@ origins = ["*"]
 app.state.ENABLE_SIGNUP = ENABLE_SIGNUP
 app.state.ENABLE_SIGNUP = ENABLE_SIGNUP
 app.state.DEFAULT_MODELS = DEFAULT_MODELS
 app.state.DEFAULT_MODELS = DEFAULT_MODELS
 app.state.DEFAULT_PROMPT_SUGGESTIONS = DEFAULT_PROMPT_SUGGESTIONS
 app.state.DEFAULT_PROMPT_SUGGESTIONS = DEFAULT_PROMPT_SUGGESTIONS
+app.state.DEFAULT_USER_ROLE = DEFAULT_USER_ROLE
+app.state.USER_PERMISSIONS = USER_PERMISSIONS
+
 
 
 app.add_middleware(
 app.add_middleware(
     CORSMiddleware,
     CORSMiddleware,

+ 40 - 4
backend/apps/web/routers/auths.py

@@ -19,7 +19,12 @@ from apps.web.models.auths import (
 )
 )
 from apps.web.models.users import Users
 from apps.web.models.users import Users
 
 
-from utils.utils import get_password_hash, get_current_user, get_admin_user, create_token
+from utils.utils import (
+    get_password_hash,
+    get_current_user,
+    get_admin_user,
+    create_token,
+)
 from utils.misc import get_gravatar_url, validate_email_format
 from utils.misc import get_gravatar_url, validate_email_format
 from constants import ERROR_MESSAGES
 from constants import ERROR_MESSAGES
 
 
@@ -116,16 +121,24 @@ async def signin(form_data: SigninForm):
 @router.post("/signup", response_model=SigninResponse)
 @router.post("/signup", response_model=SigninResponse)
 async def signup(request: Request, form_data: SignupForm):
 async def signup(request: Request, form_data: SignupForm):
     if not request.app.state.ENABLE_SIGNUP:
     if not request.app.state.ENABLE_SIGNUP:
-        raise HTTPException(status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.ACCESS_PROHIBITED)
+        raise HTTPException(
+            status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.ACCESS_PROHIBITED
+        )
 
 
     if not validate_email_format(form_data.email.lower()):
     if not validate_email_format(form_data.email.lower()):
-        raise HTTPException(status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.INVALID_EMAIL_FORMAT)
+        raise HTTPException(
+            status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.INVALID_EMAIL_FORMAT
+        )
 
 
     if Users.get_user_by_email(form_data.email.lower()):
     if Users.get_user_by_email(form_data.email.lower()):
         raise HTTPException(400, detail=ERROR_MESSAGES.EMAIL_TAKEN)
         raise HTTPException(400, detail=ERROR_MESSAGES.EMAIL_TAKEN)
 
 
     try:
     try:
-        role = "admin" if Users.get_num_users() == 0 else "pending"
+        role = (
+            "admin"
+            if Users.get_num_users() == 0
+            else request.app.state.DEFAULT_USER_ROLE
+        )
         hashed = get_password_hash(form_data.password)
         hashed = get_password_hash(form_data.password)
         user = Auths.insert_new_auth(
         user = Auths.insert_new_auth(
             form_data.email.lower(), hashed, form_data.name, role
             form_data.email.lower(), hashed, form_data.name, role
@@ -164,3 +177,26 @@ async def get_sign_up_status(request: Request, user=Depends(get_admin_user)):
 async def toggle_sign_up(request: Request, user=Depends(get_admin_user)):
 async def toggle_sign_up(request: Request, user=Depends(get_admin_user)):
     request.app.state.ENABLE_SIGNUP = not request.app.state.ENABLE_SIGNUP
     request.app.state.ENABLE_SIGNUP = not request.app.state.ENABLE_SIGNUP
     return request.app.state.ENABLE_SIGNUP
     return request.app.state.ENABLE_SIGNUP
+
+
+############################
+# Default User Role
+############################
+
+
+@router.get("/signup/user/role")
+async def get_default_user_role(request: Request, user=Depends(get_admin_user)):
+    return request.app.state.DEFAULT_USER_ROLE
+
+
+class UpdateRoleForm(BaseModel):
+    role: str
+
+
+@router.post("/signup/user/role")
+async def update_default_user_role(
+    request: Request, form_data: UpdateRoleForm, user=Depends(get_admin_user)
+):
+    if form_data.role in ["pending", "user", "admin"]:
+        request.app.state.DEFAULT_USER_ROLE = form_data.role
+    return request.app.state.DEFAULT_USER_ROLE

+ 11 - 1
backend/apps/web/routers/chats.py

@@ -165,7 +165,17 @@ async def update_chat_by_id(
 
 
 
 
 @router.delete("/{id}", response_model=bool)
 @router.delete("/{id}", response_model=bool)
-async def delete_chat_by_id(id: str, user=Depends(get_current_user)):
+async def delete_chat_by_id(request: Request, id: str, user=Depends(get_current_user)):
+
+    if (
+        user.role == "user"
+        and not request.app.state.USER_PERMISSIONS["chat"]["deletion"]
+    ):
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
+        )
+
     result = Chats.delete_chat_by_id_and_user_id(id, user.id)
     result = Chats.delete_chat_by_id_and_user_id(id, user.id)
     return result
     return result
 
 

+ 19 - 1
backend/apps/web/routers/users.py

@@ -1,4 +1,4 @@
-from fastapi import Response
+from fastapi import Response, Request
 from fastapi import Depends, FastAPI, HTTPException, status
 from fastapi import Depends, FastAPI, HTTPException, status
 from datetime import datetime, timedelta
 from datetime import datetime, timedelta
 from typing import List, Union, Optional
 from typing import List, Union, Optional
@@ -26,6 +26,24 @@ async def get_users(skip: int = 0, limit: int = 50, user=Depends(get_admin_user)
     return Users.get_users(skip, limit)
     return Users.get_users(skip, limit)
 
 
 
 
+############################
+# User Permissions
+############################
+
+
+@router.get("/permissions/user")
+async def get_user_permissions(request: Request, user=Depends(get_admin_user)):
+    return request.app.state.USER_PERMISSIONS
+
+
+@router.post("/permissions/user")
+async def update_user_permissions(
+    request: Request, form_data: dict, user=Depends(get_admin_user)
+):
+    request.app.state.USER_PERMISSIONS = form_data
+    return request.app.state.USER_PERMISSIONS
+
+
 ############################
 ############################
 # UpdateUserRole
 # UpdateUserRole
 ############################
 ############################

+ 3 - 0
backend/config.py

@@ -93,6 +93,9 @@ DEFAULT_PROMPT_SUGGESTIONS = os.environ.get(
         },
         },
     ],
     ],
 )
 )
+DEFAULT_USER_ROLE = "pending"
+USER_PERMISSIONS = {"chat": {"deletion": True}}
+
 
 
 ####################################
 ####################################
 # WEBUI_VERSION
 # WEBUI_VERSION

+ 57 - 0
src/lib/apis/auths/index.ts

@@ -178,6 +178,63 @@ export const getSignUpEnabledStatus = async (token: string) => {
 	return res;
 	return res;
 };
 };
 
 
+export const getDefaultUserRole = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/auths/signup/user/role`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.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 updateDefaultUserRole = async (token: string, role: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/auths/signup/user/role`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			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 toggleSignUpEnabledStatus = async (token: string) => {
 export const toggleSignUpEnabledStatus = async (token: string) => {
 	let error = null;
 	let error = null;
 
 

+ 1 - 1
src/lib/apis/chats/index.ts

@@ -272,7 +272,7 @@ export const deleteChatById = async (token: string, id: string) => {
 			return json;
 			return json;
 		})
 		})
 		.catch((err) => {
 		.catch((err) => {
-			error = err;
+			error = err.detail;
 
 
 			console.log(err);
 			console.log(err);
 			return null;
 			return null;

+ 57 - 0
src/lib/apis/users/index.ts

@@ -1,5 +1,62 @@
 import { WEBUI_API_BASE_URL } from '$lib/constants';
 import { WEBUI_API_BASE_URL } from '$lib/constants';
 
 
+export const getUserPermissions = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/users/permissions/user`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.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 updateUserPermissions = async (token: string, permissions: object) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/users/permissions/user`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			...permissions
+		})
+	})
+		.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 updateUserRole = async (token: string, id: string, role: string) => {
 export const updateUserRole = async (token: string, id: string, role: string) => {
 	let error = null;
 	let error = null;
 
 

+ 108 - 0
src/lib/components/admin/Settings/General.svelte

@@ -0,0 +1,108 @@
+<script lang="ts">
+	import {
+		getDefaultUserRole,
+		getSignUpEnabledStatus,
+		toggleSignUpEnabledStatus,
+		updateDefaultUserRole
+	} from '$lib/apis/auths';
+	import { onMount } from 'svelte';
+
+	export let saveHandler: Function;
+	let signUpEnabled = true;
+	let defaultUserRole = 'pending';
+
+	const toggleSignUpEnabled = async () => {
+		signUpEnabled = await toggleSignUpEnabledStatus(localStorage.token);
+	};
+
+	const updateDefaultUserRoleHandler = async (role) => {
+		defaultUserRole = await updateDefaultUserRole(localStorage.token, role);
+	};
+
+	onMount(async () => {
+		signUpEnabled = await getSignUpEnabledStatus(localStorage.token);
+		defaultUserRole = await getDefaultUserRole(localStorage.token);
+	});
+</script>
+
+<form
+	class="flex flex-col h-full justify-between space-y-3 text-sm"
+	on:submit|preventDefault={() => {
+		// console.log('submit');
+		saveHandler();
+	}}
+>
+	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
+		<div>
+			<div class=" mb-2 text-sm font-medium">General Settings</div>
+
+			<div class="  flex w-full justify-between">
+				<div class=" self-center text-xs font-medium">Enable New Sign Ups</div>
+
+				<button
+					class="p-1 px-3 text-xs flex rounded transition"
+					on:click={() => {
+						toggleSignUpEnabled();
+					}}
+					type="button"
+				>
+					{#if signUpEnabled}
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							viewBox="0 0 16 16"
+							fill="currentColor"
+							class="w-4 h-4"
+						>
+							<path
+								d="M11.5 1A3.5 3.5 0 0 0 8 4.5V7H2.5A1.5 1.5 0 0 0 1 8.5v5A1.5 1.5 0 0 0 2.5 15h7a1.5 1.5 0 0 0 1.5-1.5v-5A1.5 1.5 0 0 0 9.5 7V4.5a2 2 0 1 1 4 0v1.75a.75.75 0 0 0 1.5 0V4.5A3.5 3.5 0 0 0 11.5 1Z"
+							/>
+						</svg>
+						<span class="ml-2 self-center">Enabled</span>
+					{:else}
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							viewBox="0 0 16 16"
+							fill="currentColor"
+							class="w-4 h-4"
+						>
+							<path
+								fill-rule="evenodd"
+								d="M8 1a3.5 3.5 0 0 0-3.5 3.5V7A1.5 1.5 0 0 0 3 8.5v5A1.5 1.5 0 0 0 4.5 15h7a1.5 1.5 0 0 0 1.5-1.5v-5A1.5 1.5 0 0 0 11.5 7V4.5A3.5 3.5 0 0 0 8 1Zm2 6V4.5a2 2 0 1 0-4 0V7h4Z"
+								clip-rule="evenodd"
+							/>
+						</svg>
+
+						<span class="ml-2 self-center">Disabled</span>
+					{/if}
+				</button>
+			</div>
+
+			<div class=" flex w-full justify-between">
+				<div class=" self-center text-xs font-medium">Default User Role</div>
+				<div class="flex items-center relative">
+					<select
+						class="w-fit pr-8 rounded py-2 px-2 text-xs bg-transparent outline-none text-right"
+						bind:value={defaultUserRole}
+						placeholder="Select a theme"
+						on:change={(e) => {
+							updateDefaultUserRoleHandler(e.target.value);
+						}}
+					>
+						<option value="pending">Pending</option>
+						<option value="user">User</option>
+						<option value="admin">Admin</option>
+					</select>
+				</div>
+			</div>
+		</div>
+	</div>
+
+	<div class="flex justify-end pt-3 text-sm font-medium">
+		<button
+			class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
+			type="submit"
+		>
+			Save
+		</button>
+	</div>
+</form>

+ 82 - 0
src/lib/components/admin/Settings/Users.svelte

@@ -0,0 +1,82 @@
+<script lang="ts">
+	import { getSignUpEnabledStatus, toggleSignUpEnabledStatus } from '$lib/apis/auths';
+	import { getUserPermissions, updateUserPermissions } from '$lib/apis/users';
+	import { onMount } from 'svelte';
+
+	export let saveHandler: Function;
+
+	let permissions = {
+		chat: {
+			deletion: true
+		}
+	};
+
+	onMount(async () => {
+		permissions = await getUserPermissions(localStorage.token);
+	});
+</script>
+
+<form
+	class="flex flex-col h-full justify-between space-y-3 text-sm"
+	on:submit|preventDefault={async () => {
+		// console.log('submit');
+		await updateUserPermissions(localStorage.token, permissions);
+		saveHandler();
+	}}
+>
+	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
+		<div>
+			<div class=" mb-2 text-sm font-medium">User Permission</div>
+
+			<div class="  flex w-full justify-between">
+				<div class=" self-center text-xs font-medium">Allow Chat Deletion</div>
+
+				<button
+					class="p-1 px-3 text-xs flex rounded transition"
+					on:click={() => {
+						permissions.chat.deletion = !permissions.chat.deletion;
+					}}
+					type="button"
+				>
+					{#if permissions.chat.deletion}
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							viewBox="0 0 16 16"
+							fill="currentColor"
+							class="w-4 h-4"
+						>
+							<path
+								d="M11.5 1A3.5 3.5 0 0 0 8 4.5V7H2.5A1.5 1.5 0 0 0 1 8.5v5A1.5 1.5 0 0 0 2.5 15h7a1.5 1.5 0 0 0 1.5-1.5v-5A1.5 1.5 0 0 0 9.5 7V4.5a2 2 0 1 1 4 0v1.75a.75.75 0 0 0 1.5 0V4.5A3.5 3.5 0 0 0 11.5 1Z"
+							/>
+						</svg>
+						<span class="ml-2 self-center">Allow</span>
+					{:else}
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							viewBox="0 0 16 16"
+							fill="currentColor"
+							class="w-4 h-4"
+						>
+							<path
+								fill-rule="evenodd"
+								d="M8 1a3.5 3.5 0 0 0-3.5 3.5V7A1.5 1.5 0 0 0 3 8.5v5A1.5 1.5 0 0 0 4.5 15h7a1.5 1.5 0 0 0 1.5-1.5v-5A1.5 1.5 0 0 0 11.5 7V4.5A3.5 3.5 0 0 0 8 1Zm2 6V4.5a2 2 0 1 0-4 0V7h4Z"
+								clip-rule="evenodd"
+							/>
+						</svg>
+
+						<span class="ml-2 self-center">Don't Allow</span>
+					{/if}
+				</button>
+			</div>
+		</div>
+	</div>
+
+	<div class="flex justify-end pt-3 text-sm font-medium">
+		<button
+			class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
+			type="submit"
+		>
+			Save
+		</button>
+	</div>
+</form>

+ 107 - 0
src/lib/components/admin/SettingsModal.svelte

@@ -0,0 +1,107 @@
+<script>
+	import Modal from '../common/Modal.svelte';
+
+	import General from './Settings/General.svelte';
+	import Users from './Settings/Users.svelte';
+
+	export let show = false;
+
+	let selectedTab = 'general';
+</script>
+
+<Modal bind:show>
+	<div>
+		<div class=" flex justify-between dark:text-gray-300 px-5 py-4">
+			<div class=" text-lg font-medium self-center">Admin Settings</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>
+		<hr class=" dark:border-gray-800" />
+
+		<div class="flex flex-col md:flex-row w-full p-4 md:space-x-4">
+			<div
+				class="tabs flex flex-row overflow-x-auto space-x-1 md:space-x-0 md:space-y-1 md:flex-col flex-1 md:flex-none md:w-40 dark:text-gray-200 text-xs text-left mb-3 md:mb-0"
+			>
+				<button
+					class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
+					'general'
+						? 'bg-gray-200 dark:bg-gray-700'
+						: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
+					on:click={() => {
+						selectedTab = 'general';
+					}}
+				>
+					<div class=" self-center mr-2">
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							viewBox="0 0 16 16"
+							fill="currentColor"
+							class="w-4 h-4"
+						>
+							<path
+								fill-rule="evenodd"
+								d="M6.955 1.45A.5.5 0 0 1 7.452 1h1.096a.5.5 0 0 1 .497.45l.17 1.699c.484.12.94.312 1.356.562l1.321-1.081a.5.5 0 0 1 .67.033l.774.775a.5.5 0 0 1 .034.67l-1.08 1.32c.25.417.44.873.561 1.357l1.699.17a.5.5 0 0 1 .45.497v1.096a.5.5 0 0 1-.45.497l-1.699.17c-.12.484-.312.94-.562 1.356l1.082 1.322a.5.5 0 0 1-.034.67l-.774.774a.5.5 0 0 1-.67.033l-1.322-1.08c-.416.25-.872.44-1.356.561l-.17 1.699a.5.5 0 0 1-.497.45H7.452a.5.5 0 0 1-.497-.45l-.17-1.699a4.973 4.973 0 0 1-1.356-.562L4.108 13.37a.5.5 0 0 1-.67-.033l-.774-.775a.5.5 0 0 1-.034-.67l1.08-1.32a4.971 4.971 0 0 1-.561-1.357l-1.699-.17A.5.5 0 0 1 1 8.548V7.452a.5.5 0 0 1 .45-.497l1.699-.17c.12-.484.312-.94.562-1.356L2.629 4.107a.5.5 0 0 1 .034-.67l.774-.774a.5.5 0 0 1 .67-.033L5.43 3.71a4.97 4.97 0 0 1 1.356-.561l.17-1.699ZM6 8c0 .538.212 1.026.558 1.385l.057.057a2 2 0 0 0 2.828-2.828l-.058-.056A2 2 0 0 0 6 8Z"
+								clip-rule="evenodd"
+							/>
+						</svg>
+					</div>
+					<div class=" self-center">General</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 ===
+					'users'
+						? 'bg-gray-200 dark:bg-gray-700'
+						: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
+					on:click={() => {
+						selectedTab = 'users';
+					}}
+				>
+					<div class=" self-center mr-2">
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							viewBox="0 0 16 16"
+							fill="currentColor"
+							class="w-4 h-4"
+						>
+							<path
+								d="M8 8a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5ZM3.156 11.763c.16-.629.44-1.21.813-1.72a2.5 2.5 0 0 0-2.725 1.377c-.136.287.102.58.418.58h1.449c.01-.077.025-.156.045-.237ZM12.847 11.763c.02.08.036.16.046.237h1.446c.316 0 .554-.293.417-.579a2.5 2.5 0 0 0-2.722-1.378c.374.51.653 1.09.813 1.72ZM14 7.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0ZM3.5 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM5 13c-.552 0-1.013-.455-.876-.99a4.002 4.002 0 0 1 7.753 0c.136.535-.324.99-.877.99H5Z"
+							/>
+						</svg>
+					</div>
+					<div class=" self-center">Users</div>
+				</button>
+			</div>
+			<div class="flex-1 md:min-h-[380px]">
+				{#if selectedTab === 'general'}
+					<General
+						saveHandler={() => {
+							show = false;
+						}}
+					/>
+				{:else if selectedTab === 'users'}
+					<Users
+						saveHandler={() => {
+							show = false;
+						}}
+					/>
+				{/if}
+			</div>
+		</div>
+	</div>
+</Modal>

+ 11 - 3
src/lib/components/layout/Sidebar.svelte

@@ -15,6 +15,7 @@
 		getChatListByTagName,
 		getChatListByTagName,
 		updateChatById
 		updateChatById
 	} from '$lib/apis/chats';
 	} from '$lib/apis/chats';
+	import toast from 'svelte-french-toast';
 
 
 	let show = false;
 	let show = false;
 	let navElement;
 	let navElement;
@@ -64,10 +65,17 @@
 	};
 	};
 
 
 	const deleteChat = async (id) => {
 	const deleteChat = async (id) => {
-		goto('/');
+		const res = await deleteChatById(localStorage.token, id).catch((error) => {
+			toast.error(error);
+			chatDeleteId = null;
 
 
-		await deleteChatById(localStorage.token, id);
-		await chats.set(await getChatList(localStorage.token));
+			return null;
+		});
+
+		if (res) {
+			goto('/');
+			await chats.set(await getChatList(localStorage.token));
+		}
 	};
 	};
 
 
 	const saveSettings = async (updated) => {
 	const saveSettings = async (updated) => {

+ 19 - 41
src/routes/(app)/admin/+page.svelte

@@ -9,13 +9,14 @@
 	import { updateUserRole, getUsers, deleteUserById } from '$lib/apis/users';
 	import { updateUserRole, getUsers, deleteUserById } from '$lib/apis/users';
 	import { getSignUpEnabledStatus, toggleSignUpEnabledStatus } from '$lib/apis/auths';
 	import { getSignUpEnabledStatus, toggleSignUpEnabledStatus } from '$lib/apis/auths';
 	import EditUserModal from '$lib/components/admin/EditUserModal.svelte';
 	import EditUserModal from '$lib/components/admin/EditUserModal.svelte';
+	import SettingsModal from '$lib/components/admin/SettingsModal.svelte';
 
 
 	let loaded = false;
 	let loaded = false;
 	let users = [];
 	let users = [];
 
 
 	let selectedUser = null;
 	let selectedUser = null;
 
 
-	let signUpEnabled = true;
+	let showSettingsModal = false;
 	let showEditUserModal = false;
 	let showEditUserModal = false;
 
 
 	const updateRoleHandler = async (id, role) => {
 	const updateRoleHandler = async (id, role) => {
@@ -50,17 +51,11 @@
 		}
 		}
 	};
 	};
 
 
-	const toggleSignUpEnabled = async () => {
-		signUpEnabled = await toggleSignUpEnabledStatus(localStorage.token);
-	};
-
 	onMount(async () => {
 	onMount(async () => {
 		if ($user?.role !== 'admin') {
 		if ($user?.role !== 'admin') {
 			await goto('/');
 			await goto('/');
 		} else {
 		} else {
 			users = await getUsers(localStorage.token);
 			users = await getUsers(localStorage.token);
-
-			signUpEnabled = await getSignUpEnabledStatus(localStorage.token);
 		}
 		}
 		loaded = true;
 		loaded = true;
 	});
 	});
@@ -77,6 +72,8 @@
 	/>
 	/>
 {/key}
 {/key}
 
 
+<SettingsModal bind:show={showSettingsModal} />
+
 <div
 <div
 	class=" bg-white dark:bg-gray-900 dark:text-gray-100 min-h-screen w-full flex justify-center font-mona"
 	class=" bg-white dark:bg-gray-900 dark:text-gray-100 min-h-screen w-full flex justify-center font-mona"
 >
 >
@@ -91,42 +88,23 @@
 								class="flex items-center space-x-1 border border-gray-200 dark:border-gray-600 px-3 py-1 rounded-lg"
 								class="flex items-center space-x-1 border border-gray-200 dark:border-gray-600 px-3 py-1 rounded-lg"
 								type="button"
 								type="button"
 								on:click={() => {
 								on:click={() => {
-									toggleSignUpEnabled();
+									showSettingsModal = !showSettingsModal;
 								}}
 								}}
 							>
 							>
-								{#if signUpEnabled}
-									<svg
-										xmlns="http://www.w3.org/2000/svg"
-										viewBox="0 0 16 16"
-										fill="currentColor"
-										class="w-4 h-4"
-									>
-										<path
-											d="M11.5 1A3.5 3.5 0 0 0 8 4.5V7H2.5A1.5 1.5 0 0 0 1 8.5v5A1.5 1.5 0 0 0 2.5 15h7a1.5 1.5 0 0 0 1.5-1.5v-5A1.5 1.5 0 0 0 9.5 7V4.5a2 2 0 1 1 4 0v1.75a.75.75 0 0 0 1.5 0V4.5A3.5 3.5 0 0 0 11.5 1Z"
-										/>
-									</svg>
-
-									<div class=" text-xs">
-										New Sign Up <span class=" font-semibold">Enabled</span>
-									</div>
-								{:else}
-									<svg
-										xmlns="http://www.w3.org/2000/svg"
-										viewBox="0 0 16 16"
-										fill="currentColor"
-										class="w-4 h-4"
-									>
-										<path
-											fill-rule="evenodd"
-											d="M8 1a3.5 3.5 0 0 0-3.5 3.5V7A1.5 1.5 0 0 0 3 8.5v5A1.5 1.5 0 0 0 4.5 15h7a1.5 1.5 0 0 0 1.5-1.5v-5A1.5 1.5 0 0 0 11.5 7V4.5A3.5 3.5 0 0 0 8 1Zm2 6V4.5a2 2 0 1 0-4 0V7h4Z"
-											clip-rule="evenodd"
-										/>
-									</svg>
-
-									<div class=" text-xs">
-										New Sign Up <span class=" font-semibold">Disabled</span>
-									</div>
-								{/if}
+								<svg
+									xmlns="http://www.w3.org/2000/svg"
+									viewBox="0 0 16 16"
+									fill="currentColor"
+									class="w-4 h-4"
+								>
+									<path
+										fill-rule="evenodd"
+										d="M6.955 1.45A.5.5 0 0 1 7.452 1h1.096a.5.5 0 0 1 .497.45l.17 1.699c.484.12.94.312 1.356.562l1.321-1.081a.5.5 0 0 1 .67.033l.774.775a.5.5 0 0 1 .034.67l-1.08 1.32c.25.417.44.873.561 1.357l1.699.17a.5.5 0 0 1 .45.497v1.096a.5.5 0 0 1-.45.497l-1.699.17c-.12.484-.312.94-.562 1.356l1.082 1.322a.5.5 0 0 1-.034.67l-.774.774a.5.5 0 0 1-.67.033l-1.322-1.08c-.416.25-.872.44-1.356.561l-.17 1.699a.5.5 0 0 1-.497.45H7.452a.5.5 0 0 1-.497-.45l-.17-1.699a4.973 4.973 0 0 1-1.356-.562L4.108 13.37a.5.5 0 0 1-.67-.033l-.774-.775a.5.5 0 0 1-.034-.67l1.08-1.32a4.971 4.971 0 0 1-.561-1.357l-1.699-.17A.5.5 0 0 1 1 8.548V7.452a.5.5 0 0 1 .45-.497l1.699-.17c.12-.484.312-.94.562-1.356L2.629 4.107a.5.5 0 0 1 .034-.67l.774-.774a.5.5 0 0 1 .67-.033L5.43 3.71a4.97 4.97 0 0 1 1.356-.561l.17-1.699ZM6 8c0 .538.212 1.026.558 1.385l.057.057a2 2 0 0 0 2.828-2.828l-.058-.056A2 2 0 0 0 6 8Z"
+										clip-rule="evenodd"
+									/>
+								</svg>
+
+								<div class=" text-xs">Admin Settings</div>
 							</button>
 							</button>
 						</div>
 						</div>
 					</div>
 					</div>