forked from open-webui/open-webui
commit
ae347bb5e7
8 changed files with 153 additions and 3 deletions
|
@ -19,6 +19,7 @@ from config import (
|
||||||
DEFAULT_USER_ROLE,
|
DEFAULT_USER_ROLE,
|
||||||
ENABLE_SIGNUP,
|
ENABLE_SIGNUP,
|
||||||
USER_PERMISSIONS,
|
USER_PERMISSIONS,
|
||||||
|
WEBHOOK_URL,
|
||||||
)
|
)
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
@ -32,6 +33,7 @@ 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.DEFAULT_USER_ROLE = DEFAULT_USER_ROLE
|
||||||
app.state.USER_PERMISSIONS = USER_PERMISSIONS
|
app.state.USER_PERMISSIONS = USER_PERMISSIONS
|
||||||
|
app.state.WEBHOOK_URL = WEBHOOK_URL
|
||||||
|
|
||||||
|
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
|
|
|
@ -27,7 +27,8 @@ from utils.utils import (
|
||||||
create_token,
|
create_token,
|
||||||
)
|
)
|
||||||
from utils.misc import parse_duration, validate_email_format
|
from utils.misc import parse_duration, validate_email_format
|
||||||
from constants import ERROR_MESSAGES
|
from utils.webhook import post_webhook
|
||||||
|
from constants import ERROR_MESSAGES, WEBHOOK_MESSAGES
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
@ -155,6 +156,17 @@ async def signup(request: Request, form_data: SignupForm):
|
||||||
)
|
)
|
||||||
# response.set_cookie(key='token', value=token, httponly=True)
|
# response.set_cookie(key='token', value=token, httponly=True)
|
||||||
|
|
||||||
|
if request.app.state.WEBHOOK_URL:
|
||||||
|
post_webhook(
|
||||||
|
request.app.state.WEBHOOK_URL,
|
||||||
|
WEBHOOK_MESSAGES.USER_SIGNUP(user.name),
|
||||||
|
{
|
||||||
|
"action": "signup",
|
||||||
|
"message": WEBHOOK_MESSAGES.USER_SIGNUP(user.name),
|
||||||
|
"user": user.model_dump_json(exclude_none=True),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"token": token,
|
"token": token,
|
||||||
"token_type": "Bearer",
|
"token_type": "Bearer",
|
||||||
|
|
|
@ -302,6 +302,7 @@ MODEL_FILTER_ENABLED = os.environ.get("MODEL_FILTER_ENABLED", False)
|
||||||
MODEL_FILTER_LIST = os.environ.get("MODEL_FILTER_LIST", "")
|
MODEL_FILTER_LIST = os.environ.get("MODEL_FILTER_LIST", "")
|
||||||
MODEL_FILTER_LIST = [model.strip() for model in MODEL_FILTER_LIST.split(";")]
|
MODEL_FILTER_LIST = [model.strip() for model in MODEL_FILTER_LIST.split(";")]
|
||||||
|
|
||||||
|
WEBHOOK_URL = os.environ.get("WEBHOOK_URL", "")
|
||||||
|
|
||||||
####################################
|
####################################
|
||||||
# WEBUI_VERSION
|
# WEBUI_VERSION
|
||||||
|
|
|
@ -5,6 +5,13 @@ class MESSAGES(str, Enum):
|
||||||
DEFAULT = lambda msg="": f"{msg if msg else ''}"
|
DEFAULT = lambda msg="": f"{msg if msg else ''}"
|
||||||
|
|
||||||
|
|
||||||
|
class WEBHOOK_MESSAGES(str, Enum):
|
||||||
|
DEFAULT = lambda msg="": f"{msg if msg else ''}"
|
||||||
|
USER_SIGNUP = lambda username="": (
|
||||||
|
f"New user signed up: {username}" if username else "New user signed up"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ERROR_MESSAGES(str, Enum):
|
class ERROR_MESSAGES(str, Enum):
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return super().__str__()
|
return super().__str__()
|
||||||
|
|
|
@ -38,6 +38,7 @@ from config import (
|
||||||
FRONTEND_BUILD_DIR,
|
FRONTEND_BUILD_DIR,
|
||||||
MODEL_FILTER_ENABLED,
|
MODEL_FILTER_ENABLED,
|
||||||
MODEL_FILTER_LIST,
|
MODEL_FILTER_LIST,
|
||||||
|
WEBHOOK_URL,
|
||||||
)
|
)
|
||||||
from constants import ERROR_MESSAGES
|
from constants import ERROR_MESSAGES
|
||||||
|
|
||||||
|
@ -58,6 +59,9 @@ app = FastAPI(docs_url="/docs" if ENV == "dev" else None, redoc_url=None)
|
||||||
app.state.MODEL_FILTER_ENABLED = MODEL_FILTER_ENABLED
|
app.state.MODEL_FILTER_ENABLED = MODEL_FILTER_ENABLED
|
||||||
app.state.MODEL_FILTER_LIST = MODEL_FILTER_LIST
|
app.state.MODEL_FILTER_LIST = MODEL_FILTER_LIST
|
||||||
|
|
||||||
|
app.state.WEBHOOK_URL = WEBHOOK_URL
|
||||||
|
|
||||||
|
|
||||||
origins = ["*"]
|
origins = ["*"]
|
||||||
|
|
||||||
|
|
||||||
|
@ -178,7 +182,7 @@ class ModelFilterConfigForm(BaseModel):
|
||||||
|
|
||||||
|
|
||||||
@app.post("/api/config/model/filter")
|
@app.post("/api/config/model/filter")
|
||||||
async def get_model_filter_config(
|
async def update_model_filter_config(
|
||||||
form_data: ModelFilterConfigForm, user=Depends(get_admin_user)
|
form_data: ModelFilterConfigForm, user=Depends(get_admin_user)
|
||||||
):
|
):
|
||||||
|
|
||||||
|
@ -197,6 +201,28 @@ async def get_model_filter_config(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/webhook")
|
||||||
|
async def get_webhook_url(user=Depends(get_admin_user)):
|
||||||
|
return {
|
||||||
|
"url": app.state.WEBHOOK_URL,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class UrlForm(BaseModel):
|
||||||
|
url: str
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/webhook")
|
||||||
|
async def update_webhook_url(form_data: UrlForm, user=Depends(get_admin_user)):
|
||||||
|
app.state.WEBHOOK_URL = form_data.url
|
||||||
|
|
||||||
|
webui_app.state.WEBHOOK_URL = app.state.WEBHOOK_URL
|
||||||
|
|
||||||
|
return {
|
||||||
|
"url": app.state.WEBHOOK_URL,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/version")
|
@app.get("/api/version")
|
||||||
async def get_app_config():
|
async def get_app_config():
|
||||||
|
|
||||||
|
|
20
backend/utils/webhook.py
Normal file
20
backend/utils/webhook.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
|
def post_webhook(url: str, message: str, event_data: dict) -> bool:
|
||||||
|
try:
|
||||||
|
payload = {}
|
||||||
|
|
||||||
|
if "https://hooks.slack.com" in url:
|
||||||
|
payload["text"] = message
|
||||||
|
elif "https://discord.com/api/webhooks" in url:
|
||||||
|
payload["content"] = message
|
||||||
|
else:
|
||||||
|
payload = {**event_data}
|
||||||
|
|
||||||
|
r = requests.post(url, json=payload)
|
||||||
|
r.raise_for_status()
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
return False
|
|
@ -139,3 +139,60 @@ export const updateModelFilterConfig = async (
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getWebhookUrl = async (token: string) => {
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
const res = await fetch(`${WEBUI_BASE_URL}/api/webhook`, {
|
||||||
|
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;
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.url;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateWebhookUrl = async (token: string, url: string) => {
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
const res = await fetch(`${WEBUI_BASE_URL}/api/webhook`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
url: url
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(async (res) => {
|
||||||
|
if (!res.ok) throw await res.json();
|
||||||
|
return res.json();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
error = err;
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.url;
|
||||||
|
};
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { getWebhookUrl, updateWebhookUrl } from '$lib/apis';
|
||||||
import {
|
import {
|
||||||
getDefaultUserRole,
|
getDefaultUserRole,
|
||||||
getJWTExpiresDuration,
|
getJWTExpiresDuration,
|
||||||
|
@ -16,6 +17,8 @@
|
||||||
let defaultUserRole = 'pending';
|
let defaultUserRole = 'pending';
|
||||||
let JWTExpiresIn = '';
|
let JWTExpiresIn = '';
|
||||||
|
|
||||||
|
let webhookUrl = '';
|
||||||
|
|
||||||
const toggleSignUpEnabled = async () => {
|
const toggleSignUpEnabled = async () => {
|
||||||
signUpEnabled = await toggleSignUpEnabledStatus(localStorage.token);
|
signUpEnabled = await toggleSignUpEnabledStatus(localStorage.token);
|
||||||
};
|
};
|
||||||
|
@ -28,18 +31,23 @@
|
||||||
JWTExpiresIn = await updateJWTExpiresDuration(localStorage.token, duration);
|
JWTExpiresIn = await updateJWTExpiresDuration(localStorage.token, duration);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateWebhookUrlHandler = async () => {
|
||||||
|
webhookUrl = await updateWebhookUrl(localStorage.token, webhookUrl);
|
||||||
|
};
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
signUpEnabled = await getSignUpEnabledStatus(localStorage.token);
|
signUpEnabled = await getSignUpEnabledStatus(localStorage.token);
|
||||||
defaultUserRole = await getDefaultUserRole(localStorage.token);
|
defaultUserRole = await getDefaultUserRole(localStorage.token);
|
||||||
JWTExpiresIn = await getJWTExpiresDuration(localStorage.token);
|
JWTExpiresIn = await getJWTExpiresDuration(localStorage.token);
|
||||||
|
webhookUrl = await getWebhookUrl(localStorage.token);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form
|
<form
|
||||||
class="flex flex-col h-full justify-between space-y-3 text-sm"
|
class="flex flex-col h-full justify-between space-y-3 text-sm"
|
||||||
on:submit|preventDefault={() => {
|
on:submit|preventDefault={() => {
|
||||||
// console.log('submit');
|
|
||||||
updateJWTExpiresDurationHandler(JWTExpiresIn);
|
updateJWTExpiresDurationHandler(JWTExpiresIn);
|
||||||
|
updateWebhookUrlHandler();
|
||||||
saveHandler();
|
saveHandler();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -108,6 +116,23 @@
|
||||||
|
|
||||||
<hr class=" dark:border-gray-700 my-3" />
|
<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 py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
|
||||||
|
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=" w-full justify-between">
|
||||||
<div class="flex 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 class=" self-center text-xs font-medium">{$i18n.t('JWT Expiration')}</div>
|
||||||
|
|
Loading…
Reference in a new issue