enh/feat: toggle folders & user perm

This commit is contained in:
Timothy Jaeryang Baek 2025-11-26 22:47:48 -05:00
parent 04b337323a
commit 457af65df6
9 changed files with 61 additions and 9 deletions

View file

@ -1447,6 +1447,10 @@ USER_PERMISSIONS_FEATURES_CODE_INTERPRETER = (
== "true" == "true"
) )
USER_PERMISSIONS_FEATURES_FOLDERS = (
os.environ.get("USER_PERMISSIONS_FEATURES_FOLDERS", "True").lower() == "true"
)
USER_PERMISSIONS_FEATURES_NOTES = ( USER_PERMISSIONS_FEATURES_NOTES = (
os.environ.get("USER_PERMISSIONS_FEATURES_NOTES", "True").lower() == "true" os.environ.get("USER_PERMISSIONS_FEATURES_NOTES", "True").lower() == "true"
) )
@ -1503,12 +1507,15 @@ DEFAULT_USER_PERMISSIONS = {
"temporary_enforced": USER_PERMISSIONS_CHAT_TEMPORARY_ENFORCED, "temporary_enforced": USER_PERMISSIONS_CHAT_TEMPORARY_ENFORCED,
}, },
"features": { "features": {
# General features
"api_keys": USER_PERMISSIONS_FEATURES_API_KEYS, "api_keys": USER_PERMISSIONS_FEATURES_API_KEYS,
"folders": USER_PERMISSIONS_FEATURES_FOLDERS,
"notes": USER_PERMISSIONS_FEATURES_NOTES,
"direct_tool_servers": USER_PERMISSIONS_FEATURES_DIRECT_TOOL_SERVERS, "direct_tool_servers": USER_PERMISSIONS_FEATURES_DIRECT_TOOL_SERVERS,
# Chat features
"web_search": USER_PERMISSIONS_FEATURES_WEB_SEARCH, "web_search": USER_PERMISSIONS_FEATURES_WEB_SEARCH,
"image_generation": USER_PERMISSIONS_FEATURES_IMAGE_GENERATION, "image_generation": USER_PERMISSIONS_FEATURES_IMAGE_GENERATION,
"code_interpreter": USER_PERMISSIONS_FEATURES_CODE_INTERPRETER, "code_interpreter": USER_PERMISSIONS_FEATURES_CODE_INTERPRETER,
"notes": USER_PERMISSIONS_FEATURES_NOTES,
}, },
} }
@ -1518,6 +1525,12 @@ USER_PERMISSIONS = PersistentConfig(
DEFAULT_USER_PERMISSIONS, DEFAULT_USER_PERMISSIONS,
) )
ENABLE_FOLDERS = PersistentConfig(
"ENABLE_FOLDERS",
"folders.enable",
os.environ.get("ENABLE_FOLDERS", "True").lower() == "true",
)
ENABLE_CHANNELS = PersistentConfig( ENABLE_CHANNELS = PersistentConfig(
"ENABLE_CHANNELS", "ENABLE_CHANNELS",
"channels.enable", "channels.enable",

View file

@ -352,6 +352,7 @@ from open_webui.config import (
ENABLE_API_KEYS, ENABLE_API_KEYS,
ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS, ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS,
API_KEYS_ALLOWED_ENDPOINTS, API_KEYS_ALLOWED_ENDPOINTS,
ENABLE_FOLDERS,
ENABLE_CHANNELS, ENABLE_CHANNELS,
ENABLE_NOTES, ENABLE_NOTES,
ENABLE_COMMUNITY_SHARING, ENABLE_COMMUNITY_SHARING,
@ -767,6 +768,7 @@ app.state.config.WEBHOOK_URL = WEBHOOK_URL
app.state.config.BANNERS = WEBUI_BANNERS app.state.config.BANNERS = WEBUI_BANNERS
app.state.config.ENABLE_FOLDERS = ENABLE_FOLDERS
app.state.config.ENABLE_CHANNELS = ENABLE_CHANNELS app.state.config.ENABLE_CHANNELS = ENABLE_CHANNELS
app.state.config.ENABLE_NOTES = ENABLE_NOTES app.state.config.ENABLE_NOTES = ENABLE_NOTES
app.state.config.ENABLE_COMMUNITY_SHARING = ENABLE_COMMUNITY_SHARING app.state.config.ENABLE_COMMUNITY_SHARING = ENABLE_COMMUNITY_SHARING
@ -1842,6 +1844,7 @@ async def get_app_config(request: Request):
**( **(
{ {
"enable_direct_connections": app.state.config.ENABLE_DIRECT_CONNECTIONS, "enable_direct_connections": app.state.config.ENABLE_DIRECT_CONNECTIONS,
"enable_folders": app.state.config.ENABLE_FOLDERS,
"enable_channels": app.state.config.ENABLE_CHANNELS, "enable_channels": app.state.config.ENABLE_CHANNELS,
"enable_notes": app.state.config.ENABLE_NOTES, "enable_notes": app.state.config.ENABLE_NOTES,
"enable_web_search": app.state.config.ENABLE_WEB_SEARCH, "enable_web_search": app.state.config.ENABLE_WEB_SEARCH,

View file

@ -900,6 +900,7 @@ async def get_admin_config(request: Request, user=Depends(get_admin_user)):
"JWT_EXPIRES_IN": request.app.state.config.JWT_EXPIRES_IN, "JWT_EXPIRES_IN": request.app.state.config.JWT_EXPIRES_IN,
"ENABLE_COMMUNITY_SHARING": request.app.state.config.ENABLE_COMMUNITY_SHARING, "ENABLE_COMMUNITY_SHARING": request.app.state.config.ENABLE_COMMUNITY_SHARING,
"ENABLE_MESSAGE_RATING": request.app.state.config.ENABLE_MESSAGE_RATING, "ENABLE_MESSAGE_RATING": request.app.state.config.ENABLE_MESSAGE_RATING,
"ENABLE_FOLDERS": request.app.state.config.ENABLE_FOLDERS,
"ENABLE_CHANNELS": request.app.state.config.ENABLE_CHANNELS, "ENABLE_CHANNELS": request.app.state.config.ENABLE_CHANNELS,
"ENABLE_NOTES": request.app.state.config.ENABLE_NOTES, "ENABLE_NOTES": request.app.state.config.ENABLE_NOTES,
"ENABLE_USER_WEBHOOKS": request.app.state.config.ENABLE_USER_WEBHOOKS, "ENABLE_USER_WEBHOOKS": request.app.state.config.ENABLE_USER_WEBHOOKS,
@ -921,6 +922,7 @@ class AdminConfig(BaseModel):
JWT_EXPIRES_IN: str JWT_EXPIRES_IN: str
ENABLE_COMMUNITY_SHARING: bool ENABLE_COMMUNITY_SHARING: bool
ENABLE_MESSAGE_RATING: bool ENABLE_MESSAGE_RATING: bool
ENABLE_FOLDERS: bool
ENABLE_CHANNELS: bool ENABLE_CHANNELS: bool
ENABLE_NOTES: bool ENABLE_NOTES: bool
ENABLE_USER_WEBHOOKS: bool ENABLE_USER_WEBHOOKS: bool
@ -945,6 +947,7 @@ async def update_admin_config(
form_data.API_KEYS_ALLOWED_ENDPOINTS form_data.API_KEYS_ALLOWED_ENDPOINTS
) )
request.app.state.config.ENABLE_FOLDERS = form_data.ENABLE_FOLDERS
request.app.state.config.ENABLE_CHANNELS = form_data.ENABLE_CHANNELS request.app.state.config.ENABLE_CHANNELS = form_data.ENABLE_CHANNELS
request.app.state.config.ENABLE_NOTES = form_data.ENABLE_NOTES request.app.state.config.ENABLE_NOTES = form_data.ENABLE_NOTES
@ -987,6 +990,7 @@ async def update_admin_config(
"JWT_EXPIRES_IN": request.app.state.config.JWT_EXPIRES_IN, "JWT_EXPIRES_IN": request.app.state.config.JWT_EXPIRES_IN,
"ENABLE_COMMUNITY_SHARING": request.app.state.config.ENABLE_COMMUNITY_SHARING, "ENABLE_COMMUNITY_SHARING": request.app.state.config.ENABLE_COMMUNITY_SHARING,
"ENABLE_MESSAGE_RATING": request.app.state.config.ENABLE_MESSAGE_RATING, "ENABLE_MESSAGE_RATING": request.app.state.config.ENABLE_MESSAGE_RATING,
"ENABLE_FOLDERS": request.app.state.config.ENABLE_FOLDERS,
"ENABLE_CHANNELS": request.app.state.config.ENABLE_CHANNELS, "ENABLE_CHANNELS": request.app.state.config.ENABLE_CHANNELS,
"ENABLE_NOTES": request.app.state.config.ENABLE_NOTES, "ENABLE_NOTES": request.app.state.config.ENABLE_NOTES,
"ENABLE_USER_WEBHOOKS": request.app.state.config.ENABLE_USER_WEBHOOKS, "ENABLE_USER_WEBHOOKS": request.app.state.config.ENABLE_USER_WEBHOOKS,

View file

@ -46,7 +46,23 @@ router = APIRouter()
@router.get("/", response_model=list[FolderNameIdResponse]) @router.get("/", response_model=list[FolderNameIdResponse])
async def get_folders(user=Depends(get_verified_user)): async def get_folders(request: Request, user=Depends(get_verified_user)):
if request.app.state.config.ENABLE_FOLDERS is False:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
if user.role != "admin" and not has_permission(
user.id,
"features.folders",
request.app.state.config.USER_PERMISSIONS,
):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
folders = Folders.get_folders_by_user_id(user.id) folders = Folders.get_folders_by_user_id(user.id)
# Verify folder data integrity # Verify folder data integrity

View file

@ -219,11 +219,13 @@ class ChatPermissions(BaseModel):
class FeaturesPermissions(BaseModel): class FeaturesPermissions(BaseModel):
api_keys: bool = False api_keys: bool = False
folders: bool = True
notes: bool = True
direct_tool_servers: bool = False direct_tool_servers: bool = False
web_search: bool = True web_search: bool = True
image_generation: bool = True image_generation: bool = True
code_interpreter: bool = True code_interpreter: bool = True
notes: bool = True
class UserPermissions(BaseModel): class UserPermissions(BaseModel):

View file

@ -676,6 +676,14 @@
<Switch bind:state={adminConfig.ENABLE_MESSAGE_RATING} /> <Switch bind:state={adminConfig.ENABLE_MESSAGE_RATING} />
</div> </div>
<div class="mb-2.5 flex w-full items-center justify-between pr-2">
<div class=" self-center text-xs font-medium">
{$i18n.t('Folders')}
</div>
<Switch bind:state={adminConfig.ENABLE_FOLDERS} />
</div>
<div class="mb-2.5 flex w-full items-center justify-between pr-2"> <div class="mb-2.5 flex w-full items-center justify-between pr-2">
<div class=" self-center text-xs font-medium"> <div class=" self-center text-xs font-medium">
{$i18n.t('Notes')} ({$i18n.t('Beta')}) {$i18n.t('Notes')} ({$i18n.t('Beta')})

View file

@ -84,11 +84,12 @@
}, },
features: { features: {
api_keys: false, api_keys: false,
folders: true,
notes: true,
direct_tool_servers: false, direct_tool_servers: false,
web_search: true, web_search: true,
image_generation: true, image_generation: true,
code_interpreter: true, code_interpreter: true
notes: true
} }
}; };

View file

@ -54,11 +54,12 @@
}, },
features: { features: {
api_keys: false, api_keys: false,
folders: true,
notes: true,
direct_tool_servers: false, direct_tool_servers: false,
web_search: true, web_search: true,
image_generation: true, image_generation: true,
code_interpreter: true, code_interpreter: true
notes: true
} }
}; };

View file

@ -63,6 +63,7 @@
import Note from '../icons/Note.svelte'; import Note from '../icons/Note.svelte';
import { slide } from 'svelte/transition'; import { slide } from 'svelte/transition';
import HotkeyHint from '../common/HotkeyHint.svelte'; import HotkeyHint from '../common/HotkeyHint.svelte';
import { key } from 'vega';
const BREAKPOINT = 768; const BREAKPOINT = 768;
@ -90,8 +91,11 @@
} }
const initFolders = async () => { const initFolders = async () => {
if ($config?.features?.enable_folders === false) {
return;
}
const folderList = await getFolders(localStorage.token).catch((error) => { const folderList = await getFolders(localStorage.token).catch((error) => {
toast.error(`${error}`);
return []; return [];
}); });
_folders.set(folderList.sort((a, b) => b.updated_at - a.updated_at)); _folders.set(folderList.sort((a, b) => b.updated_at - a.updated_at));
@ -932,7 +936,7 @@
</Folder> </Folder>
{/if} {/if}
{#if folders} {#if $config?.features?.enable_folders && ($user?.role === 'admin' || ($user?.permissions?.features?.folders ?? true)) && Object.keys(folders).length > 0}
<Folder <Folder
id="sidebar-folders" id="sidebar-folders"
className="px-2 mt-0.5" className="px-2 mt-0.5"