forked from open-webui/open-webui
Merge pull request #400 from ollama-webui/edit-user
feat: edit user support
This commit is contained in:
commit
e9753b87a8
9 changed files with 371 additions and 43 deletions
|
@ -75,26 +75,20 @@ class SignupForm(BaseModel):
|
||||||
|
|
||||||
|
|
||||||
class AuthsTable:
|
class AuthsTable:
|
||||||
|
|
||||||
def __init__(self, db):
|
def __init__(self, db):
|
||||||
self.db = db
|
self.db = db
|
||||||
self.db.create_tables([Auth])
|
self.db.create_tables([Auth])
|
||||||
|
|
||||||
def insert_new_auth(self,
|
def insert_new_auth(
|
||||||
email: str,
|
self, email: str, password: str, name: str, role: str = "pending"
|
||||||
password: str,
|
) -> Optional[UserModel]:
|
||||||
name: str,
|
|
||||||
role: str = "pending") -> Optional[UserModel]:
|
|
||||||
print("insert_new_auth")
|
print("insert_new_auth")
|
||||||
|
|
||||||
id = str(uuid.uuid4())
|
id = str(uuid.uuid4())
|
||||||
|
|
||||||
auth = AuthModel(**{
|
auth = AuthModel(
|
||||||
"id": id,
|
**{"id": id, "email": email, "password": password, "active": True}
|
||||||
"email": email,
|
)
|
||||||
"password": password,
|
|
||||||
"active": True
|
|
||||||
})
|
|
||||||
result = Auth.create(**auth.model_dump())
|
result = Auth.create(**auth.model_dump())
|
||||||
|
|
||||||
user = Users.insert_new_user(id, name, email, role)
|
user = Users.insert_new_user(id, name, email, role)
|
||||||
|
@ -104,8 +98,7 @@ class AuthsTable:
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def authenticate_user(self, email: str,
|
def authenticate_user(self, email: str, password: str) -> Optional[UserModel]:
|
||||||
password: str) -> Optional[UserModel]:
|
|
||||||
print("authenticate_user", email)
|
print("authenticate_user", email)
|
||||||
try:
|
try:
|
||||||
auth = Auth.get(Auth.email == email, Auth.active == True)
|
auth = Auth.get(Auth.email == email, Auth.active == True)
|
||||||
|
@ -129,6 +122,15 @@ class AuthsTable:
|
||||||
except:
|
except:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def update_email_by_id(self, id: str, email: str) -> bool:
|
||||||
|
try:
|
||||||
|
query = Auth.update(email=email).where(Auth.id == id)
|
||||||
|
result = query.execute()
|
||||||
|
|
||||||
|
return True if result == 1 else False
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
def delete_auth_by_id(self, id: str) -> bool:
|
def delete_auth_by_id(self, id: str) -> bool:
|
||||||
try:
|
try:
|
||||||
# Delete User
|
# Delete User
|
||||||
|
@ -137,8 +139,7 @@ class AuthsTable:
|
||||||
if result:
|
if result:
|
||||||
# Delete Auth
|
# Delete Auth
|
||||||
query = Auth.delete().where(Auth.id == id)
|
query = Auth.delete().where(Auth.id == id)
|
||||||
query.execute(
|
query.execute() # Remove the rows, return number of rows removed.
|
||||||
) # Remove the rows, return number of rows removed.
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -44,17 +44,21 @@ class UserRoleUpdateForm(BaseModel):
|
||||||
role: str
|
role: str
|
||||||
|
|
||||||
|
|
||||||
class UsersTable:
|
class UserUpdateForm(BaseModel):
|
||||||
|
name: str
|
||||||
|
email: str
|
||||||
|
profile_image_url: str
|
||||||
|
password: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class UsersTable:
|
||||||
def __init__(self, db):
|
def __init__(self, db):
|
||||||
self.db = db
|
self.db = db
|
||||||
self.db.create_tables([User])
|
self.db.create_tables([User])
|
||||||
|
|
||||||
def insert_new_user(self,
|
def insert_new_user(
|
||||||
id: str,
|
self, id: str, name: str, email: str, role: str = "pending"
|
||||||
name: str,
|
) -> Optional[UserModel]:
|
||||||
email: str,
|
|
||||||
role: str = "pending") -> Optional[UserModel]:
|
|
||||||
user = UserModel(
|
user = UserModel(
|
||||||
**{
|
**{
|
||||||
"id": id,
|
"id": id,
|
||||||
|
@ -63,7 +67,8 @@ class UsersTable:
|
||||||
"role": role,
|
"role": role,
|
||||||
"profile_image_url": get_gravatar_url(email),
|
"profile_image_url": get_gravatar_url(email),
|
||||||
"timestamp": int(time.time()),
|
"timestamp": int(time.time()),
|
||||||
})
|
}
|
||||||
|
)
|
||||||
result = User.create(**user.model_dump())
|
result = User.create(**user.model_dump())
|
||||||
if result:
|
if result:
|
||||||
return user
|
return user
|
||||||
|
@ -93,8 +98,7 @@ class UsersTable:
|
||||||
def get_num_users(self) -> Optional[int]:
|
def get_num_users(self) -> Optional[int]:
|
||||||
return User.select().count()
|
return User.select().count()
|
||||||
|
|
||||||
def update_user_role_by_id(self, id: str,
|
def update_user_role_by_id(self, id: str, role: str) -> Optional[UserModel]:
|
||||||
role: str) -> Optional[UserModel]:
|
|
||||||
try:
|
try:
|
||||||
query = User.update(role=role).where(User.id == id)
|
query = User.update(role=role).where(User.id == id)
|
||||||
query.execute()
|
query.execute()
|
||||||
|
@ -104,6 +108,16 @@ class UsersTable:
|
||||||
except:
|
except:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def update_user_by_id(self, id: str, updated: dict) -> Optional[UserModel]:
|
||||||
|
try:
|
||||||
|
query = User.update(**updated).where(User.id == id)
|
||||||
|
query.execute()
|
||||||
|
|
||||||
|
user = User.get(User.id == id)
|
||||||
|
return UserModel(**model_to_dict(user))
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
def delete_user_by_id(self, id: str) -> bool:
|
def delete_user_by_id(self, id: str) -> bool:
|
||||||
try:
|
try:
|
||||||
# Delete User Chats
|
# Delete User Chats
|
||||||
|
@ -112,8 +126,7 @@ class UsersTable:
|
||||||
if result:
|
if result:
|
||||||
# Delete User
|
# Delete User
|
||||||
query = User.delete().where(User.id == id)
|
query = User.delete().where(User.id == id)
|
||||||
query.execute(
|
query.execute() # Remove the rows, return number of rows removed.
|
||||||
) # Remove the rows, return number of rows removed.
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -8,10 +8,10 @@ from pydantic import BaseModel
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from apps.web.models.users import UserModel, UserRoleUpdateForm, Users
|
from apps.web.models.users import UserModel, UserUpdateForm, UserRoleUpdateForm, Users
|
||||||
from apps.web.models.auths import Auths
|
from apps.web.models.auths import Auths
|
||||||
|
|
||||||
from utils.utils import get_current_user
|
from utils.utils import get_current_user, get_password_hash
|
||||||
from constants import ERROR_MESSAGES
|
from constants import ERROR_MESSAGES
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
@ -22,9 +22,7 @@ router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
@router.get("/", response_model=List[UserModel])
|
@router.get("/", response_model=List[UserModel])
|
||||||
async def get_users(skip: int = 0,
|
async def get_users(skip: int = 0, limit: int = 50, user=Depends(get_current_user)):
|
||||||
limit: int = 50,
|
|
||||||
user=Depends(get_current_user)):
|
|
||||||
if user.role != "admin":
|
if user.role != "admin":
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_403_FORBIDDEN,
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
@ -34,25 +32,58 @@ async def get_users(skip: int = 0,
|
||||||
|
|
||||||
|
|
||||||
############################
|
############################
|
||||||
# UpdateUserRole
|
# UpdateUserById
|
||||||
############################
|
############################
|
||||||
|
|
||||||
|
|
||||||
@router.post("/update/role", response_model=Optional[UserModel])
|
@router.post("/{user_id}/update", response_model=Optional[UserModel])
|
||||||
async def update_user_role(form_data: UserRoleUpdateForm,
|
async def update_user_by_id(
|
||||||
user=Depends(get_current_user)):
|
user_id: str, form_data: UserUpdateForm, session_user=Depends(get_current_user)
|
||||||
if user.role != "admin":
|
):
|
||||||
|
if session_user.role != "admin":
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_403_FORBIDDEN,
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||||
)
|
)
|
||||||
|
|
||||||
if user.id != form_data.id:
|
user = Users.get_user_by_id(user_id)
|
||||||
return Users.update_user_role_by_id(form_data.id, form_data.role)
|
|
||||||
|
if user:
|
||||||
|
if form_data.email != user.email:
|
||||||
|
email_user = Users.get_user_by_email(form_data.email)
|
||||||
|
if email_user:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail=ERROR_MESSAGES.EMAIL_TAKEN,
|
||||||
|
)
|
||||||
|
|
||||||
|
if form_data.password:
|
||||||
|
hashed = get_password_hash(form_data.password)
|
||||||
|
print(hashed)
|
||||||
|
Auths.update_user_password_by_id(user_id, hashed)
|
||||||
|
|
||||||
|
Auths.update_email_by_id(user_id, form_data.email)
|
||||||
|
updated_user = Users.update_user_by_id(
|
||||||
|
user_id,
|
||||||
|
{
|
||||||
|
"name": form_data.name,
|
||||||
|
"email": form_data.email,
|
||||||
|
"profile_image_url": form_data.profile_image_url,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if updated_user:
|
||||||
|
return updated_user
|
||||||
|
else:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail=ERROR_MESSAGES.DEFAULT(),
|
||||||
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_403_FORBIDDEN,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
detail=ERROR_MESSAGES.ACTION_PROHIBITED,
|
detail=ERROR_MESSAGES.USER_NOT_FOUND,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
11
package-lock.json
generated
11
package-lock.json
generated
|
@ -9,6 +9,7 @@
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sveltejs/adapter-node": "^1.3.1",
|
"@sveltejs/adapter-node": "^1.3.1",
|
||||||
|
"dayjs": "^1.11.10",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"highlight.js": "^11.9.0",
|
"highlight.js": "^11.9.0",
|
||||||
"idb": "^7.1.1",
|
"idb": "^7.1.1",
|
||||||
|
@ -1577,6 +1578,11 @@
|
||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dayjs": {
|
||||||
|
"version": "1.11.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz",
|
||||||
|
"integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ=="
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.3.4",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
|
@ -4940,6 +4946,11 @@
|
||||||
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
|
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"dayjs": {
|
||||||
|
"version": "1.11.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz",
|
||||||
|
"integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ=="
|
||||||
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
"version": "4.3.4",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
|
|
|
@ -40,6 +40,7 @@
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sveltejs/adapter-node": "^1.3.1",
|
"@sveltejs/adapter-node": "^1.3.1",
|
||||||
|
"dayjs": "^1.11.10",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"highlight.js": "^11.9.0",
|
"highlight.js": "^11.9.0",
|
||||||
"idb": "^7.1.1",
|
"idb": "^7.1.1",
|
||||||
|
|
|
@ -84,3 +84,43 @@ export const deleteUserById = async (token: string, userId: string) => {
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type UserUpdateForm = {
|
||||||
|
profile_image_url: string;
|
||||||
|
email: string;
|
||||||
|
name: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateUserById = async (token: string, userId: string, user: UserUpdateForm) => {
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
const res = await fetch(`${WEBUI_API_BASE_URL}/users/${userId}/update`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
profile_image_url: user.profile_image_url,
|
||||||
|
email: user.email,
|
||||||
|
name: user.name,
|
||||||
|
password: user.password !== '' ? user.password : undefined
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.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;
|
||||||
|
};
|
||||||
|
|
172
src/lib/components/admin/EditUserModal.svelte
Normal file
172
src/lib/components/admin/EditUserModal.svelte
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import toast from 'svelte-french-toast';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
|
import { updateUserById } from '$lib/apis/users';
|
||||||
|
import Modal from '../common/Modal.svelte';
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
export let show = false;
|
||||||
|
export let selectedUser;
|
||||||
|
export let sessionUser;
|
||||||
|
|
||||||
|
let _user = {
|
||||||
|
profile_image_url: '',
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
password: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
const submitHandler = async () => {
|
||||||
|
const res = await updateUserById(localStorage.token, selectedUser.id, _user).catch((error) => {
|
||||||
|
toast.error(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
dispatch('save');
|
||||||
|
show = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if (selectedUser) {
|
||||||
|
_user = selectedUser;
|
||||||
|
_user.password = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Modal size="sm" bind:show>
|
||||||
|
<div>
|
||||||
|
<div class=" flex justify-between dark:text-gray-300 px-5 py-4">
|
||||||
|
<div class=" text-lg font-medium self-center">Edit 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>
|
||||||
|
<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">
|
||||||
|
<form
|
||||||
|
class="flex flex-col w-full"
|
||||||
|
on:submit|preventDefault={() => {
|
||||||
|
submitHandler();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class=" flex items-center rounded-md py-2 px-4 w-full">
|
||||||
|
<div class=" self-center mr-5">
|
||||||
|
<img
|
||||||
|
src={selectedUser.profile_image_url}
|
||||||
|
class=" max-w-[55px] object-cover rounded-full"
|
||||||
|
alt="User profile"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class=" self-center capitalize font-semibold">{selectedUser.name}</div>
|
||||||
|
|
||||||
|
<div class="text-xs text-gray-500">
|
||||||
|
Created at {dayjs(selectedUser.timestamp * 1000).format('MMMM DD, YYYY')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class=" dark:border-gray-800 my-3 w-full" />
|
||||||
|
|
||||||
|
<div class=" flex flex-col space-y-1.5">
|
||||||
|
<div class="flex flex-col w-full">
|
||||||
|
<div class=" mb-1 text-xs text-gray-500">Email</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="email"
|
||||||
|
bind:value={_user.email}
|
||||||
|
autocomplete="off"
|
||||||
|
required
|
||||||
|
disabled={_user.id == sessionUser.id}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col w-full">
|
||||||
|
<div class=" mb-1 text-xs text-gray-500">Name</div>
|
||||||
|
|
||||||
|
<div class="flex-1">
|
||||||
|
<input
|
||||||
|
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
|
||||||
|
type="text"
|
||||||
|
bind:value={_user.name}
|
||||||
|
autocomplete="off"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col w-full">
|
||||||
|
<div class=" mb-1 text-xs text-gray-500">New Password</div>
|
||||||
|
|
||||||
|
<div class="flex-1">
|
||||||
|
<input
|
||||||
|
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
|
||||||
|
type="password"
|
||||||
|
bind:value={_user.password}
|
||||||
|
autocomplete="new-password"
|
||||||
|
/>
|
||||||
|
</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>
|
||||||
|
</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>
|
|
@ -3,8 +3,18 @@
|
||||||
import { fade, blur } from 'svelte/transition';
|
import { fade, blur } from 'svelte/transition';
|
||||||
|
|
||||||
export let show = true;
|
export let show = true;
|
||||||
|
export let size = 'md';
|
||||||
|
|
||||||
let mounted = false;
|
let mounted = false;
|
||||||
|
|
||||||
|
const sizeToWidth = (size) => {
|
||||||
|
if (size === 'sm') {
|
||||||
|
return 'w-[30rem]';
|
||||||
|
} else {
|
||||||
|
return 'w-[40rem]';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
mounted = true;
|
mounted = true;
|
||||||
});
|
});
|
||||||
|
@ -28,7 +38,9 @@
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="m-auto rounded-xl max-w-full w-[40rem] mx-2 bg-gray-50 dark:bg-gray-900 shadow-3xl"
|
class="m-auto rounded-xl max-w-full {sizeToWidth(
|
||||||
|
size
|
||||||
|
)} mx-2 bg-gray-50 dark:bg-gray-900 shadow-3xl"
|
||||||
transition:fade={{ delay: 100, duration: 200 }}
|
transition:fade={{ delay: 100, duration: 200 }}
|
||||||
on:click={(e) => {
|
on:click={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
|
@ -8,11 +8,15 @@
|
||||||
|
|
||||||
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';
|
||||||
|
|
||||||
let loaded = false;
|
let loaded = false;
|
||||||
let users = [];
|
let users = [];
|
||||||
|
|
||||||
|
let selectedUser = null;
|
||||||
|
|
||||||
let signUpEnabled = true;
|
let signUpEnabled = true;
|
||||||
|
let showEditUserModal = false;
|
||||||
|
|
||||||
const updateRoleHandler = async (id, role) => {
|
const updateRoleHandler = async (id, role) => {
|
||||||
const res = await updateUserRole(localStorage.token, id, role).catch((error) => {
|
const res = await updateUserRole(localStorage.token, id, role).catch((error) => {
|
||||||
|
@ -25,6 +29,17 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const editUserPasswordHandler = async (id, password) => {
|
||||||
|
const res = await deleteUserById(localStorage.token, id).catch((error) => {
|
||||||
|
toast.error(error);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
if (res) {
|
||||||
|
users = await getUsers(localStorage.token);
|
||||||
|
toast.success('Successfully updated');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const deleteUserHandler = async (id) => {
|
const deleteUserHandler = async (id) => {
|
||||||
const res = await deleteUserById(localStorage.token, id).catch((error) => {
|
const res = await deleteUserById(localStorage.token, id).catch((error) => {
|
||||||
toast.error(error);
|
toast.error(error);
|
||||||
|
@ -51,6 +66,17 @@
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
{#key selectedUser}
|
||||||
|
<EditUserModal
|
||||||
|
bind:show={showEditUserModal}
|
||||||
|
{selectedUser}
|
||||||
|
sessionUser={$user}
|
||||||
|
on:save={async () => {
|
||||||
|
users = await getUsers(localStorage.token);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/key}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class=" bg-white dark:bg-gray-800 dark:text-gray-100 min-h-screen w-full flex justify-center font-mona"
|
class=" bg-white dark:bg-gray-800 dark:text-gray-100 min-h-screen w-full flex justify-center font-mona"
|
||||||
>
|
>
|
||||||
|
@ -154,7 +180,28 @@
|
||||||
}}>{user.role}</button
|
}}>{user.role}</button
|
||||||
>
|
>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 text-center flex justify-center">
|
<td class="px-6 py-4 space-x-1 text-center flex justify-center">
|
||||||
|
<button
|
||||||
|
class="self-center w-fit text-sm p-1.5 border dark:border-gray-600 rounded-xl flex"
|
||||||
|
on:click={async () => {
|
||||||
|
showEditUserModal = !showEditUserModal;
|
||||||
|
selectedUser = user;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<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="M11.013 2.513a1.75 1.75 0 0 1 2.475 2.474L6.226 12.25a2.751 2.751 0 0 1-.892.596l-2.047.848a.75.75 0 0 1-.98-.98l.848-2.047a2.75 2.75 0 0 1 .596-.892l7.262-7.261Z"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="self-center w-fit text-sm p-1.5 border dark:border-gray-600 rounded-xl flex"
|
class="self-center w-fit text-sm p-1.5 border dark:border-gray-600 rounded-xl flex"
|
||||||
on:click={async () => {
|
on:click={async () => {
|
||||||
|
|
Loading…
Reference in a new issue