forked from open-webui/open-webui
Merge pull request #726 from ollama-webui/disable-chat-delete
feat: admin settings
This commit is contained in:
commit
9704a5754e
13 changed files with 527 additions and 52 deletions
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
############################
|
############################
|
||||||
|
|
|
@ -93,12 +93,15 @@ DEFAULT_PROMPT_SUGGESTIONS = os.environ.get(
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
DEFAULT_USER_ROLE = "pending"
|
||||||
|
USER_PERMISSIONS = {"chat": {"deletion": True}}
|
||||||
|
|
||||||
|
|
||||||
####################################
|
####################################
|
||||||
# WEBUI_VERSION
|
# WEBUI_VERSION
|
||||||
####################################
|
####################################
|
||||||
|
|
||||||
WEBUI_VERSION = os.environ.get("WEBUI_VERSION", "v1.0.0-alpha.92")
|
WEBUI_VERSION = os.environ.get("WEBUI_VERSION", "v1.0.0-alpha.100")
|
||||||
|
|
||||||
####################################
|
####################################
|
||||||
# WEBUI_AUTH (Required for security)
|
# WEBUI_AUTH (Required for security)
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
src/lib/components/admin/Settings/General.svelte
Normal file
108
src/lib/components/admin/Settings/General.svelte
Normal file
|
@ -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
src/lib/components/admin/Settings/Users.svelte
Normal file
82
src/lib/components/admin/Settings/Users.svelte
Normal file
|
@ -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 Permissions</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
src/lib/components/admin/SettingsModal.svelte
Normal file
107
src/lib/components/admin/SettingsModal.svelte
Normal file
|
@ -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>
|
|
@ -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);
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
goto('/');
|
||||||
await chats.set(await getChatList(localStorage.token));
|
await chats.set(await getChatList(localStorage.token));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveSettings = async (updated) => {
|
const saveSettings = async (updated) => {
|
||||||
|
|
|
@ -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,25 +88,9 @@
|
||||||
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
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 16 16"
|
viewBox="0 0 16 16"
|
||||||
|
@ -118,15 +99,12 @@
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
fill-rule="evenodd"
|
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"
|
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"
|
clip-rule="evenodd"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
<div class=" text-xs">
|
<div class=" text-xs">Admin Settings</div>
|
||||||
New Sign Up <span class=" font-semibold">Disabled</span>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue