mirror of
				https://github.com/open-webui/open-webui
				synced 2025-06-26 18:26:48 +00:00 
			
		
		
		
	feat: admin panel added
This commit is contained in:
		
							parent
							
								
									8547b7807d
								
							
						
					
					
						commit
						07d2c9871f
					
				@ -1,7 +1,7 @@
 | 
			
		||||
from fastapi import FastAPI, Request, Depends, HTTPException
 | 
			
		||||
from fastapi.middleware.cors import CORSMiddleware
 | 
			
		||||
 | 
			
		||||
from apps.web.routers import auths
 | 
			
		||||
from apps.web.routers import auths, users
 | 
			
		||||
from config import OLLAMA_WEBUI_VERSION, OLLAMA_WEBUI_AUTH
 | 
			
		||||
 | 
			
		||||
app = FastAPI()
 | 
			
		||||
@ -18,6 +18,7 @@ app.add_middleware(
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
app.include_router(auths.router, prefix="/auths", tags=["auths"])
 | 
			
		||||
app.include_router(users.router, prefix="/users", tags=["users"])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.get("/")
 | 
			
		||||
 | 
			
		||||
@ -27,6 +27,11 @@ class UserModel(BaseModel):
 | 
			
		||||
####################
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserRoleUpdateForm(BaseModel):
 | 
			
		||||
    id: str
 | 
			
		||||
    role: str
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UsersTable:
 | 
			
		||||
    def __init__(self, db):
 | 
			
		||||
        self.db = db
 | 
			
		||||
@ -71,10 +76,19 @@ class UsersTable:
 | 
			
		||||
    def get_users(self, skip: int = 0, limit: int = 50) -> Optional[UserModel]:
 | 
			
		||||
        return [
 | 
			
		||||
            UserModel(**user)
 | 
			
		||||
            for user in list(self.table.find({}, {"_id": False}))
 | 
			
		||||
            .skip(skip)
 | 
			
		||||
            .limit(limit)
 | 
			
		||||
            for user in list(
 | 
			
		||||
                self.table.find({}, {"_id": False}).skip(skip).limit(limit)
 | 
			
		||||
            )
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
    def update_user_by_id(self, id: str, updated: dict) -> Optional[UserModel]:
 | 
			
		||||
        user = self.table.find_one_and_update(
 | 
			
		||||
            {"id": id}, {"$set": updated}, return_document=ReturnDocument.AFTER
 | 
			
		||||
        )
 | 
			
		||||
        return UserModel(**user)
 | 
			
		||||
 | 
			
		||||
    def update_user_role_by_id(self, id: str, role: str) -> Optional[UserModel]:
 | 
			
		||||
        return self.update_user_by_id(id, {"role": role})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Users = UsersTable(DB)
 | 
			
		||||
 | 
			
		||||
@ -8,15 +8,6 @@ from pydantic import BaseModel
 | 
			
		||||
import time
 | 
			
		||||
import uuid
 | 
			
		||||
 | 
			
		||||
from constants import ERROR_MESSAGES
 | 
			
		||||
from utils.utils import (
 | 
			
		||||
    get_password_hash,
 | 
			
		||||
    bearer_scheme,
 | 
			
		||||
    create_token,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
from utils.misc import get_gravatar_url
 | 
			
		||||
 | 
			
		||||
from apps.web.models.auths import (
 | 
			
		||||
    SigninForm,
 | 
			
		||||
    SignupForm,
 | 
			
		||||
@ -25,13 +16,19 @@ from apps.web.models.auths import (
 | 
			
		||||
    Auths,
 | 
			
		||||
)
 | 
			
		||||
from apps.web.models.users import Users
 | 
			
		||||
import config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from utils.utils import (
 | 
			
		||||
    get_password_hash,
 | 
			
		||||
    bearer_scheme,
 | 
			
		||||
    create_token,
 | 
			
		||||
)
 | 
			
		||||
from utils.misc import get_gravatar_url
 | 
			
		||||
from constants import ERROR_MESSAGES
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
router = APIRouter()
 | 
			
		||||
 | 
			
		||||
DB = config.DB
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
############################
 | 
			
		||||
# GetSessionUser
 | 
			
		||||
############################
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										75
									
								
								backend/apps/web/routers/users.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								backend/apps/web/routers/users.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,75 @@
 | 
			
		||||
from fastapi import Response
 | 
			
		||||
from fastapi import Depends, FastAPI, HTTPException, status
 | 
			
		||||
from datetime import datetime, timedelta
 | 
			
		||||
from typing import List, Union, Optional
 | 
			
		||||
 | 
			
		||||
from fastapi import APIRouter
 | 
			
		||||
from pydantic import BaseModel
 | 
			
		||||
import time
 | 
			
		||||
import uuid
 | 
			
		||||
 | 
			
		||||
from apps.web.models.users import UserModel, UserRoleUpdateForm, Users
 | 
			
		||||
 | 
			
		||||
from utils.utils import (
 | 
			
		||||
    get_password_hash,
 | 
			
		||||
    bearer_scheme,
 | 
			
		||||
    create_token,
 | 
			
		||||
)
 | 
			
		||||
from constants import ERROR_MESSAGES
 | 
			
		||||
 | 
			
		||||
router = APIRouter()
 | 
			
		||||
 | 
			
		||||
############################
 | 
			
		||||
# GetUsers
 | 
			
		||||
############################
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@router.get("/", response_model=List[UserModel])
 | 
			
		||||
async def get_users(skip: int = 0, limit: int = 50, cred=Depends(bearer_scheme)):
 | 
			
		||||
    token = cred.credentials
 | 
			
		||||
    user = Users.get_user_by_token(token)
 | 
			
		||||
 | 
			
		||||
    if user:
 | 
			
		||||
        if user.role == "admin":
 | 
			
		||||
            return Users.get_users(skip, limit)
 | 
			
		||||
        else:
 | 
			
		||||
            raise HTTPException(
 | 
			
		||||
                status_code=status.HTTP_403_FORBIDDEN,
 | 
			
		||||
                detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
 | 
			
		||||
            )
 | 
			
		||||
    else:
 | 
			
		||||
        raise HTTPException(
 | 
			
		||||
            status_code=status.HTTP_401_UNAUTHORIZED,
 | 
			
		||||
            detail=ERROR_MESSAGES.INVALID_TOKEN,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
############################
 | 
			
		||||
# UpdateUserRole
 | 
			
		||||
############################
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@router.post("/update/role", response_model=Optional[UserModel])
 | 
			
		||||
async def update_user_role(form_data: UserRoleUpdateForm, cred=Depends(bearer_scheme)):
 | 
			
		||||
    token = cred.credentials
 | 
			
		||||
    user = Users.get_user_by_token(token)
 | 
			
		||||
 | 
			
		||||
    if user:
 | 
			
		||||
        if user.role == "admin":
 | 
			
		||||
            if user.id != form_data.id:
 | 
			
		||||
                return Users.update_user_role_by_id(form_data.id, form_data.role)
 | 
			
		||||
            else:
 | 
			
		||||
                raise HTTPException(
 | 
			
		||||
                    status_code=status.HTTP_403_FORBIDDEN,
 | 
			
		||||
                    detail=ERROR_MESSAGES.ACTION_PROHIBITED,
 | 
			
		||||
                )
 | 
			
		||||
        else:
 | 
			
		||||
            raise HTTPException(
 | 
			
		||||
                status_code=status.HTTP_403_FORBIDDEN,
 | 
			
		||||
                detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
 | 
			
		||||
            )
 | 
			
		||||
    else:
 | 
			
		||||
        raise HTTPException(
 | 
			
		||||
            status_code=status.HTTP_401_UNAUTHORIZED,
 | 
			
		||||
            detail=ERROR_MESSAGES.INVALID_TOKEN,
 | 
			
		||||
        )
 | 
			
		||||
@ -13,6 +13,8 @@ class ERROR_MESSAGES(str, Enum):
 | 
			
		||||
    INVALID_CRED = "The email or password provided is incorrect. Please check for typos and try logging in again."
 | 
			
		||||
    UNAUTHORIZED = "401 Unauthorized"
 | 
			
		||||
    ACCESS_PROHIBITED = "You do not have permission to access this resource. Please contact your administrator for assistance."
 | 
			
		||||
    NOT_FOUND = "We could not find what you're looking for :/"
 | 
			
		||||
    ACTION_PROHIBITED = (
 | 
			
		||||
        "The requested action has been restricted as a security measure."
 | 
			
		||||
    )
 | 
			
		||||
    USER_NOT_FOUND = "We could not find what you're looking for :/"
 | 
			
		||||
    MALICIOUS = "Unusual activities detected, please try again in a few minutes."
 | 
			
		||||
 | 
			
		||||
@ -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…
	
		Reference in New Issue
	
	Block a user