From 25336f85f3caf6f193d4e7b48ee939d25f954cce Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" <timothyjrbeck@gmail.com> Date: Mon, 3 Jun 2024 21:17:43 -0700 Subject: [PATCH] feat: admin details in account pending overlay --- backend/apps/webui/main.py | 7 + backend/apps/webui/routers/auths.py | 119 ++++---- backend/apps/webui/routers/users.py | 7 +- backend/config.py | 14 + backend/main.py | 18 +- src/lib/apis/auths/index.ts | 82 ++++++ .../components/admin/Settings/General.svelte | 259 ++++++------------ .../layout/Overlay/AccountPending.svelte | 59 ++++ src/routes/(app)/+layout.svelte | 46 +--- 9 files changed, 333 insertions(+), 278 deletions(-) create mode 100644 src/lib/components/layout/Overlay/AccountPending.svelte diff --git a/backend/apps/webui/main.py b/backend/apps/webui/main.py index b823859a6..6ec9bbace 100644 --- a/backend/apps/webui/main.py +++ b/backend/apps/webui/main.py @@ -14,6 +14,8 @@ from apps.webui.routers import ( ) from config import ( WEBUI_BUILD_HASH, + SHOW_ADMIN_DETAILS, + ADMIN_EMAIL, WEBUI_AUTH, DEFAULT_MODELS, DEFAULT_PROMPT_SUGGESTIONS, @@ -37,6 +39,11 @@ app.state.config = AppConfig() app.state.config.ENABLE_SIGNUP = ENABLE_SIGNUP app.state.config.JWT_EXPIRES_IN = JWT_EXPIRES_IN + +app.state.config.SHOW_ADMIN_DETAILS = SHOW_ADMIN_DETAILS +app.state.config.ADMIN_EMAIL = ADMIN_EMAIL + + app.state.config.DEFAULT_MODELS = DEFAULT_MODELS app.state.config.DEFAULT_PROMPT_SUGGESTIONS = DEFAULT_PROMPT_SUGGESTIONS app.state.config.DEFAULT_USER_ROLE = DEFAULT_USER_ROLE diff --git a/backend/apps/webui/routers/auths.py b/backend/apps/webui/routers/auths.py index ce9b92061..d45879a24 100644 --- a/backend/apps/webui/routers/auths.py +++ b/backend/apps/webui/routers/auths.py @@ -269,73 +269,88 @@ async def add_user(form_data: AddUserForm, user=Depends(get_admin_user)): raise HTTPException(500, detail=ERROR_MESSAGES.DEFAULT(err)) +############################ +# GetAdminDetails +############################ + + +@router.get("/admin/details") +async def get_admin_details(request: Request, user=Depends(get_current_user)): + if request.app.state.config.SHOW_ADMIN_DETAILS: + admin_email = request.app.state.config.ADMIN_EMAIL + admin_name = None + + print(admin_email, admin_name) + + if admin_email: + admin = Users.get_user_by_email(admin_email) + if admin: + admin_name = admin.name + else: + admin = Users.get_first_user() + if admin: + admin_email = admin.email + admin_name = admin.name + + return { + "name": admin_name, + "email": admin_email, + } + else: + raise HTTPException(400, detail=ERROR_MESSAGES.ACTION_PROHIBITED) + + ############################ # ToggleSignUp ############################ -@router.get("/signup/enabled", response_model=bool) -async def get_sign_up_status(request: Request, user=Depends(get_admin_user)): - return request.app.state.config.ENABLE_SIGNUP +@router.get("/admin/config") +async def get_admin_config(request: Request, user=Depends(get_admin_user)): + return { + "SHOW_ADMIN_DETAILS": request.app.state.config.SHOW_ADMIN_DETAILS, + "ENABLE_SIGNUP": request.app.state.config.ENABLE_SIGNUP, + "DEFAULT_USER_ROLE": request.app.state.config.DEFAULT_USER_ROLE, + "JWT_EXPIRES_IN": request.app.state.config.JWT_EXPIRES_IN, + "ENABLE_COMMUNITY_SHARING": request.app.state.config.ENABLE_COMMUNITY_SHARING, + } -@router.get("/signup/enabled/toggle", response_model=bool) -async def toggle_sign_up(request: Request, user=Depends(get_admin_user)): - request.app.state.config.ENABLE_SIGNUP = not request.app.state.config.ENABLE_SIGNUP - return request.app.state.config.ENABLE_SIGNUP +class AdminConfig(BaseModel): + SHOW_ADMIN_DETAILS: bool + ENABLE_SIGNUP: bool + DEFAULT_USER_ROLE: str + JWT_EXPIRES_IN: str + ENABLE_COMMUNITY_SHARING: bool -############################ -# 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.config.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) +@router.post("/admin/config") +async def update_admin_config( + request: Request, form_data: AdminConfig, user=Depends(get_admin_user) ): - if form_data.role in ["pending", "user", "admin"]: - request.app.state.config.DEFAULT_USER_ROLE = form_data.role - return request.app.state.config.DEFAULT_USER_ROLE + request.app.state.config.SHOW_ADMIN_DETAILS = form_data.SHOW_ADMIN_DETAILS + request.app.state.config.ENABLE_SIGNUP = form_data.ENABLE_SIGNUP + if form_data.DEFAULT_USER_ROLE in ["pending", "user", "admin"]: + request.app.state.config.DEFAULT_USER_ROLE = form_data.DEFAULT_USER_ROLE -############################ -# JWT Expiration -############################ - - -@router.get("/token/expires") -async def get_token_expires_duration(request: Request, user=Depends(get_admin_user)): - return request.app.state.config.JWT_EXPIRES_IN - - -class UpdateJWTExpiresDurationForm(BaseModel): - duration: str - - -@router.post("/token/expires/update") -async def update_token_expires_duration( - request: Request, - form_data: UpdateJWTExpiresDurationForm, - user=Depends(get_admin_user), -): pattern = r"^(-1|0|(-?\d+(\.\d+)?)(ms|s|m|h|d|w))$" # Check if the input string matches the pattern - if re.match(pattern, form_data.duration): - request.app.state.config.JWT_EXPIRES_IN = form_data.duration - return request.app.state.config.JWT_EXPIRES_IN - else: - return request.app.state.config.JWT_EXPIRES_IN + if re.match(pattern, form_data.JWT_EXPIRES_IN): + request.app.state.config.JWT_EXPIRES_IN = form_data.JWT_EXPIRES_IN + + request.app.state.config.ENABLE_COMMUNITY_SHARING = ( + form_data.ENABLE_COMMUNITY_SHARING + ) + + return { + "SHOW_ADMIN_DETAILS": request.app.state.config.SHOW_ADMIN_DETAILS, + "ENABLE_SIGNUP": request.app.state.config.ENABLE_SIGNUP, + "DEFAULT_USER_ROLE": request.app.state.config.DEFAULT_USER_ROLE, + "JWT_EXPIRES_IN": request.app.state.config.JWT_EXPIRES_IN, + "ENABLE_COMMUNITY_SHARING": request.app.state.config.ENABLE_COMMUNITY_SHARING, + } ############################ diff --git a/backend/apps/webui/routers/users.py b/backend/apps/webui/routers/users.py index cd17e3a7c..eccafde10 100644 --- a/backend/apps/webui/routers/users.py +++ b/backend/apps/webui/routers/users.py @@ -19,7 +19,12 @@ from apps.webui.models.users import ( from apps.webui.models.auths import Auths from apps.webui.models.chats import Chats -from utils.utils import get_verified_user, get_password_hash, get_admin_user +from utils.utils import ( + get_verified_user, + get_password_hash, + get_current_user, + get_admin_user, +) from constants import ERROR_MESSAGES from config import SRC_LOG_LEVELS diff --git a/backend/config.py b/backend/config.py index 7c073f68f..dd3bc9e4b 100644 --- a/backend/config.py +++ b/backend/config.py @@ -601,6 +601,20 @@ WEBUI_BANNERS = PersistentConfig( [BannerModel(**banner) for banner in json.loads("[]")], ) + +SHOW_ADMIN_DETAILS = PersistentConfig( + "SHOW_ADMIN_DETAILS", + "auth.admin.show", + os.environ.get("SHOW_ADMIN_DETAILS", "true").lower() == "true", +) + +ADMIN_EMAIL = PersistentConfig( + "ADMIN_EMAIL", + "auth.admin.email", + os.environ.get("ADMIN_EMAIL", None), +) + + #################################### # WEBUI_SECRET_KEY #################################### diff --git a/backend/main.py b/backend/main.py index 080269898..4e9d1adf9 100644 --- a/backend/main.py +++ b/backend/main.py @@ -879,23 +879,7 @@ class UrlForm(BaseModel): async def update_webhook_url(form_data: UrlForm, user=Depends(get_admin_user)): app.state.config.WEBHOOK_URL = form_data.url webui_app.state.WEBHOOK_URL = app.state.config.WEBHOOK_URL - - return { - "url": app.state.config.WEBHOOK_URL, - } - - -@app.get("/api/community_sharing", response_model=bool) -async def get_community_sharing_status(request: Request, user=Depends(get_admin_user)): - return webui_app.state.config.ENABLE_COMMUNITY_SHARING - - -@app.get("/api/community_sharing/toggle", response_model=bool) -async def toggle_community_sharing(request: Request, user=Depends(get_admin_user)): - webui_app.state.config.ENABLE_COMMUNITY_SHARING = ( - not webui_app.state.config.ENABLE_COMMUNITY_SHARING - ) - return webui_app.state.config.ENABLE_COMMUNITY_SHARING + return {"url": app.state.config.WEBHOOK_URL} @app.get("/api/version") diff --git a/src/lib/apis/auths/index.ts b/src/lib/apis/auths/index.ts index 26feb29b6..e202115b4 100644 --- a/src/lib/apis/auths/index.ts +++ b/src/lib/apis/auths/index.ts @@ -1,5 +1,87 @@ import { WEBUI_API_BASE_URL } from '$lib/constants'; +export const getAdminDetails = async (token: string) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/auths/admin/details`, { + 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 getAdminConfig = async (token: string) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/auths/admin/config`, { + 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 updateAdminConfig = async (token: string, body: object) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/auths/admin/config`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}` + }, + body: JSON.stringify(body) + }) + .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 getSessionUser = async (token: string) => { let error = null; diff --git a/src/lib/components/admin/Settings/General.svelte b/src/lib/components/admin/Settings/General.svelte index 572ff82ad..a7ffdabfb 100644 --- a/src/lib/components/admin/Settings/General.svelte +++ b/src/lib/components/admin/Settings/General.svelte @@ -6,61 +6,44 @@ updateWebhookUrl } from '$lib/apis'; import { + getAdminConfig, getDefaultUserRole, getJWTExpiresDuration, getSignUpEnabledStatus, toggleSignUpEnabledStatus, + updateAdminConfig, updateDefaultUserRole, updateJWTExpiresDuration } from '$lib/apis/auths'; + import Switch from '$lib/components/common/Switch.svelte'; import { onMount, getContext } from 'svelte'; const i18n = getContext('i18n'); export let saveHandler: Function; - let signUpEnabled = true; - let defaultUserRole = 'pending'; - let JWTExpiresIn = ''; + let adminConfig = null; let webhookUrl = ''; - let communitySharingEnabled = true; - const toggleSignUpEnabled = async () => { - signUpEnabled = await toggleSignUpEnabledStatus(localStorage.token); - }; - - const updateDefaultUserRoleHandler = async (role) => { - defaultUserRole = await updateDefaultUserRole(localStorage.token, role); - }; - - const updateJWTExpiresDurationHandler = async (duration) => { - JWTExpiresIn = await updateJWTExpiresDuration(localStorage.token, duration); - }; - - const updateWebhookUrlHandler = async () => { + const updateHandler = async () => { webhookUrl = await updateWebhookUrl(localStorage.token, webhookUrl); - }; + const res = await updateAdminConfig(localStorage.token, adminConfig); - const toggleCommunitySharingEnabled = async () => { - communitySharingEnabled = await toggleCommunitySharingEnabledStatus(localStorage.token); + if (res) { + toast.success(i18n.t('Settings updated successfully')); + } else { + toast.error(i18n.t('Failed to update settings')); + } }; onMount(async () => { await Promise.all([ (async () => { - signUpEnabled = await getSignUpEnabledStatus(localStorage.token); - })(), - (async () => { - defaultUserRole = await getDefaultUserRole(localStorage.token); - })(), - (async () => { - JWTExpiresIn = await getJWTExpiresDuration(localStorage.token); + adminConfig = await getAdminConfig(localStorage.token); })(), + (async () => { webhookUrl = await getWebhookUrl(localStorage.token); - })(), - (async () => { - communitySharingEnabled = await getCommunitySharingEnabledStatus(localStorage.token); })() ]); }); @@ -69,156 +52,94 @@ <form class="flex flex-col h-full justify-between space-y-3 text-sm" on:submit|preventDefault={() => { - updateJWTExpiresDurationHandler(JWTExpiresIn); - updateWebhookUrlHandler(); + updateHandler(); saveHandler(); }} > - <div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80"> - <div> - <div class=" mb-2 text-sm font-medium">{$i18n.t('General Settings')}</div> + <div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[22rem]"> + {#if adminConfig !== null} + <div> + <div class=" mb-3 text-sm font-medium">{$i18n.t('General Settings')}</div> - <div class=" flex w-full justify-between"> - <div class=" self-center text-xs font-medium">{$i18n.t('Enable New Sign Ups')}</div> + <div class=" flex w-full justify-between pr-2"> + <div class=" self-center text-xs font-medium">{$i18n.t('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" + <Switch bind:state={adminConfig.ENABLE_SIGNUP} /> + </div> + + <div class=" my-3 flex w-full justify-between"> + <div class=" self-center text-xs font-medium">{$i18n.t('Default User Role')}</div> + <div class="flex items-center relative"> + <select + class="dark:bg-gray-900 w-fit pr-8 rounded px-2 text-xs bg-transparent outline-none text-right" + bind:value={adminConfig.DEFAULT_USER_ROLE} + placeholder="Select a role" > - <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">{$i18n.t('Enabled')}</span> - {:else} - <svg - xmlns="http://www.w3.org/2000/svg" - viewBox="0 0 16 16" - fill="currentColor" - class="w-4 h-4" + <option value="pending">{$i18n.t('pending')}</option> + <option value="user">{$i18n.t('user')}</option> + <option value="admin">{$i18n.t('admin')}</option> + </select> + </div> + </div> + + <hr class=" dark:border-gray-850 my-2" /> + + <div class="my-3 flex w-full items-center justify-between pr-2"> + <div class=" self-center text-xs font-medium"> + {$i18n.t('Show Admin Details in Account Pending Overlay')} + </div> + + <Switch bind:state={adminConfig.SHOW_ADMIN_DETAILS} /> + </div> + + <div class="my-3 flex w-full items-center justify-between pr-2"> + <div class=" self-center text-xs font-medium">{$i18n.t('Enable Community Sharing')}</div> + + <Switch bind:state={adminConfig.ENABLE_COMMUNITY_SHARING} /> + </div> + + <hr class=" dark:border-gray-850 my-2" /> + + <div class=" w-full justify-between"> + <div class="flex w-full justify-between"> + <div class=" self-center text-xs font-medium">{$i18n.t('JWT Expiration')}</div> + </div> + + <div class="flex mt-2 space-x-2"> + <input + class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none" + type="text" + placeholder={`e.g.) "30m","1h", "10d". `} + bind:value={adminConfig.JWT_EXPIRES_IN} + /> + </div> + + <div class="mt-2 text-xs text-gray-400 dark:text-gray-500"> + {$i18n.t('Valid time units:')} + <span class=" text-gray-300 font-medium" + >{$i18n.t("'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.")}</span > - <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> + </div> - <span class="ml-2 self-center">{$i18n.t('Disabled')}</span> - {/if} - </button> - </div> + <hr class=" dark:border-gray-850 my-2" /> - <div class=" flex w-full justify-between"> - <div class=" self-center text-xs font-medium">{$i18n.t('Default User Role')}</div> - <div class="flex items-center relative"> - <select - class="dark:bg-gray-900 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">{$i18n.t('pending')}</option> - <option value="user">{$i18n.t('user')}</option> - <option value="admin">{$i18n.t('admin')}</option> - </select> + <div class=" w-full justify-between"> + <div class="flex w-full justify-between"> + <div class=" self-center text-xs font-medium">{$i18n.t('Webhook URL')}</div> + </div> + + <div class="flex mt-2 space-x-2"> + <input + class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none" + type="text" + placeholder={`https://example.com/webhook`} + bind:value={webhookUrl} + /> + </div> </div> </div> - - <div class=" flex w-full justify-between"> - <div class=" self-center text-xs font-medium">{$i18n.t('Enable Community Sharing')}</div> - - <button - class="p-1 px-3 text-xs flex rounded transition" - on:click={() => { - toggleCommunitySharingEnabled(); - }} - type="button" - > - {#if communitySharingEnabled} - <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">{$i18n.t('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">{$i18n.t('Disabled')}</span> - {/if} - </button> - </div> - - <hr class=" dark:border-gray-700 my-3" /> - - <div class=" w-full justify-between"> - <div class="flex w-full justify-between"> - <div class=" self-center text-xs font-medium">{$i18n.t('Webhook URL')}</div> - </div> - - <div class="flex mt-2 space-x-2"> - <input - class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none" - type="text" - placeholder={`https://example.com/webhook`} - bind:value={webhookUrl} - /> - </div> - </div> - - <hr class=" dark:border-gray-700 my-3" /> - - <div class=" w-full justify-between"> - <div class="flex w-full justify-between"> - <div class=" self-center text-xs font-medium">{$i18n.t('JWT Expiration')}</div> - </div> - - <div class="flex mt-2 space-x-2"> - <input - class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none" - type="text" - placeholder={`e.g.) "30m","1h", "10d". `} - bind:value={JWTExpiresIn} - /> - </div> - - <div class="mt-2 text-xs text-gray-400 dark:text-gray-500"> - {$i18n.t('Valid time units:')} - <span class=" text-gray-300 font-medium" - >{$i18n.t("'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.")}</span - > - </div> - </div> - </div> + {/if} </div> <div class="flex justify-end pt-3 text-sm font-medium"> diff --git a/src/lib/components/layout/Overlay/AccountPending.svelte b/src/lib/components/layout/Overlay/AccountPending.svelte new file mode 100644 index 000000000..79ab23493 --- /dev/null +++ b/src/lib/components/layout/Overlay/AccountPending.svelte @@ -0,0 +1,59 @@ +<script lang="ts"> + import { getAdminDetails } from '$lib/apis/auths'; + import { onMount, tick, getContext } from 'svelte'; + + const i18n = getContext('i18n'); + + let adminDetails = null; + + onMount(async () => { + adminDetails = await getAdminDetails(localStorage.token).catch((err) => { + console.error(err); + return null; + }); + }); +</script> + +<div class="fixed w-full h-full flex z-[999]"> + <div + class="absolute w-full h-full backdrop-blur-lg bg-white/10 dark:bg-gray-900/50 flex justify-center" + > + <div class="m-auto pb-10 flex flex-col justify-center"> + <div class="max-w-md"> + <div class="text-center dark:text-white text-2xl font-medium z-50"> + Account Activation Pending<br /> Contact Admin for WebUI Access + </div> + + <div class=" mt-4 text-center text-sm dark:text-gray-200 w-full"> + Your account status is currently pending activation.<br /> To access the WebUI, please reach + out to the administrator. Admins can manage user statuses from the Admin Panel. + </div> + + {#if adminDetails} + <div class="mt-4 text-sm font-medium text-center"> + <div>Admin: {adminDetails.name} ({adminDetails.email})</div> + </div> + {/if} + + <div class=" mt-6 mx-auto relative group w-fit"> + <button + class="relative z-20 flex px-5 py-2 rounded-full bg-white border border-gray-100 dark:border-none hover:bg-gray-100 text-gray-700 transition font-medium text-sm" + on:click={async () => { + location.href = '/'; + }} + > + {$i18n.t('Check Again')} + </button> + + <button + class="text-xs text-center w-full mt-2 text-gray-400 underline" + on:click={async () => { + localStorage.removeItem('token'); + location.href = '/auth'; + }}>{$i18n.t('Sign Out')}</button + > + </div> + </div> + </div> + </div> +</div> diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index 8f0efe95e..8c1f33468 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -37,6 +37,8 @@ import { getBanners } from '$lib/apis/configs'; import { getUserSettings } from '$lib/apis/users'; import Help from '$lib/components/layout/Help.svelte'; + import AccountPending from '$lib/components/layout/Overlay/AccountPending.svelte'; + import { error } from '@sveltejs/kit'; const i18n = getContext('i18n'); @@ -74,7 +76,10 @@ // IndexedDB Not Found } - const userSettings = await getUserSettings(localStorage.token); + const userSettings = await getUserSettings(localStorage.token).catch((error) => { + console.error(error); + return null; + }); if (userSettings) { await settings.set(userSettings.ui); @@ -186,44 +191,7 @@ > {#if loaded} {#if !['user', 'admin'].includes($user.role)} - <div class="fixed w-full h-full flex z-[999]"> - <div - class="absolute w-full h-full backdrop-blur-lg bg-white/10 dark:bg-gray-900/50 flex justify-center" - > - <div class="m-auto pb-10 flex flex-col justify-center"> - <div class="max-w-md"> - <div class="text-center dark:text-white text-2xl font-medium z-50"> - Account Activation Pending<br /> Contact Admin for WebUI Access - </div> - - <div class=" mt-4 text-center text-sm dark:text-gray-200 w-full"> - Your account status is currently pending activation. To access the WebUI, please - reach out to the administrator. Admins can manage user statuses from the Admin - Panel. - </div> - - <div class=" mt-6 mx-auto relative group w-fit"> - <button - class="relative z-20 flex px-5 py-2 rounded-full bg-white border border-gray-100 dark:border-none hover:bg-gray-100 text-gray-700 transition font-medium text-sm" - on:click={async () => { - location.href = '/'; - }} - > - {$i18n.t('Check Again')} - </button> - - <button - class="text-xs text-center w-full mt-2 text-gray-400 underline" - on:click={async () => { - localStorage.removeItem('token'); - location.href = '/auth'; - }}>{$i18n.t('Sign Out')}</button - > - </div> - </div> - </div> - </div> - </div> + <AccountPending /> {:else if localDBChats.length > 0} <div class="fixed w-full h-full flex z-50"> <div