This commit is contained in:
Timothy Jaeryang Baek 2024-11-14 20:51:49 -08:00
parent dff85c733d
commit b80ec76435
7 changed files with 145 additions and 178 deletions

View File

@ -739,12 +739,12 @@ DEFAULT_USER_ROLE = PersistentConfig(
os.getenv("DEFAULT_USER_ROLE", "pending"), os.getenv("DEFAULT_USER_ROLE", "pending"),
) )
USER_PERMISSIONS_CHAT_DELETION = ( USER_PERMISSIONS_CHAT_DELETE = (
os.environ.get("USER_PERMISSIONS_CHAT_DELETION", "True").lower() == "true" os.environ.get("USER_PERMISSIONS_CHAT_DELETE", "True").lower() == "true"
) )
USER_PERMISSIONS_CHAT_EDITING = ( USER_PERMISSIONS_CHAT_EDIT = (
os.environ.get("USER_PERMISSIONS_CHAT_EDITING", "True").lower() == "true" os.environ.get("USER_PERMISSIONS_CHAT_EDIT", "True").lower() == "true"
) )
USER_PERMISSIONS_CHAT_TEMPORARY = ( USER_PERMISSIONS_CHAT_TEMPORARY = (
@ -753,11 +753,11 @@ USER_PERMISSIONS_CHAT_TEMPORARY = (
USER_PERMISSIONS = PersistentConfig( USER_PERMISSIONS = PersistentConfig(
"USER_PERMISSIONS", "USER_PERMISSIONS",
"ui.user_permissions", "user.permissions",
{ {
"chat": { "chat": {
"deletion": USER_PERMISSIONS_CHAT_DELETION, "deletion": USER_PERMISSIONS_CHAT_DELETE,
"editing": USER_PERMISSIONS_CHAT_EDITING, "editing": USER_PERMISSIONS_CHAT_EDIT,
"temporary": USER_PERMISSIONS_CHAT_TEMPORARY, "temporary": USER_PERMISSIONS_CHAT_TEMPORARY,
} }
}, },
@ -785,18 +785,6 @@ DEFAULT_ARENA_MODEL = {
}, },
} }
ENABLE_MODEL_FILTER = PersistentConfig(
"ENABLE_MODEL_FILTER",
"model_filter.enable",
os.environ.get("ENABLE_MODEL_FILTER", "False").lower() == "true",
)
MODEL_FILTER_LIST = os.environ.get("MODEL_FILTER_LIST", "")
MODEL_FILTER_LIST = PersistentConfig(
"MODEL_FILTER_LIST",
"model_filter.list",
[model.strip() for model in MODEL_FILTER_LIST.split(";")],
)
WEBHOOK_URL = PersistentConfig( WEBHOOK_URL = PersistentConfig(
"WEBHOOK_URL", "webhook_url", os.environ.get("WEBHOOK_URL", "") "WEBHOOK_URL", "webhook_url", os.environ.get("WEBHOOK_URL", "")
) )

View File

@ -70,13 +70,11 @@ from open_webui.config import (
DEFAULT_LOCALE, DEFAULT_LOCALE,
ENABLE_ADMIN_CHAT_ACCESS, ENABLE_ADMIN_CHAT_ACCESS,
ENABLE_ADMIN_EXPORT, ENABLE_ADMIN_EXPORT,
ENABLE_MODEL_FILTER,
ENABLE_OLLAMA_API, ENABLE_OLLAMA_API,
ENABLE_OPENAI_API, ENABLE_OPENAI_API,
ENABLE_TAGS_GENERATION, ENABLE_TAGS_GENERATION,
ENV, ENV,
FRONTEND_BUILD_DIR, FRONTEND_BUILD_DIR,
MODEL_FILTER_LIST,
OAUTH_PROVIDERS, OAUTH_PROVIDERS,
ENABLE_SEARCH_QUERY, ENABLE_SEARCH_QUERY,
SEARCH_QUERY_GENERATION_PROMPT_TEMPLATE, SEARCH_QUERY_GENERATION_PROMPT_TEMPLATE,
@ -194,9 +192,6 @@ app.state.config = AppConfig()
app.state.config.ENABLE_OPENAI_API = ENABLE_OPENAI_API app.state.config.ENABLE_OPENAI_API = ENABLE_OPENAI_API
app.state.config.ENABLE_OLLAMA_API = ENABLE_OLLAMA_API app.state.config.ENABLE_OLLAMA_API = ENABLE_OLLAMA_API
app.state.config.ENABLE_MODEL_FILTER = ENABLE_MODEL_FILTER
app.state.config.MODEL_FILTER_LIST = MODEL_FILTER_LIST
app.state.config.WEBHOOK_URL = WEBHOOK_URL app.state.config.WEBHOOK_URL = WEBHOOK_URL
app.state.config.TASK_MODEL = TASK_MODEL app.state.config.TASK_MODEL = TASK_MODEL

View File

@ -48,69 +48,38 @@
let showCreateGroupModal = false; let showCreateGroupModal = false;
let showDefaultPermissionsModal = false; let showDefaultPermissionsModal = false;
const setGroups = async () => {
groups = await getGroups(localStorage.token);
};
const addGroupHandler = async (group) => {
const res = await createNewGroup(localStorage.token, group).catch((error) => {
toast.error(error);
return null;
});
if (res) {
toast.success($i18n.t('Group created successfully'));
groups = await getGroups(localStorage.token);
}
};
const updateDefaultPermissionsHandler = async (permissions) => {
console.log(permissions);
};
onMount(async () => { onMount(async () => {
if ($user?.role !== 'admin') { if ($user?.role !== 'admin') {
await goto('/'); await goto('/');
} else { } else {
groups = await getGroups(localStorage.token); await setGroups();
// [
// {
// id: '1',
// name: 'Group A',
// description: 'Group A description',
// permissions: {
// model: {
// enable_filter: false, // boolean
// ids: [], // array of strings
// default_id: null // null or string
// },
// workspace: {
// models: false, // boolean
// knowledge: false, // boolean
// prompts: false // boolean
// },
// chat: {
// delete: true, // boolean
// edit: true, // boolean
// temporary: true // boolean
// }
// },
// user_ids: ['1', '2', '3'], // array of strings
// admin_ids: ['1'] // array of strings
// },
// {
// id: '2',
// name: 'Moderators',
// description: 'Moderators description',
// permissions: {
// model: {
// enable_filter: false, // boolean
// ids: [], // array of strings
// default_id: null // null or string
// },
// workspace: {
// models: false, // boolean
// knowledge: false, // boolean
// prompts: false // boolean
// },
// chat: {
// delete: true, // boolean
// edit: true, // boolean
// temporary: true // boolean
// }
// },
// user_ids: ['1', '5', '6'], // array of strings
// admin_ids: ['1'] // array of strings
// }
// ];
} }
loaded = true; loaded = true;
}); });
</script> </script>
{#if loaded} {#if loaded}
<AddGroupModal bind:show={showCreateGroupModal} /> <AddGroupModal bind:show={showCreateGroupModal} onSubmit={addGroupHandler} />
<div class="mt-0.5 mb-2 gap-1 flex flex-col md:flex-row justify-between"> <div class="mt-0.5 mb-2 gap-1 flex flex-col md:flex-row justify-between">
<div class="flex md:self-center text-lg font-medium px-0.5"> <div class="flex md:self-center text-lg font-medium px-0.5">
{$i18n.t('Groups')} {$i18n.t('Groups')}
@ -196,7 +165,7 @@
{#each filteredGroups as group} {#each filteredGroups as group}
<div class="my-2"> <div class="my-2">
<GroupItem {group} {users} /> <GroupItem {group} {users} {setGroups} />
</div> </div>
{/each} {/each}
</div> </div>
@ -204,7 +173,12 @@
<hr class="mb-2 border-gray-50 dark:border-gray-850" /> <hr class="mb-2 border-gray-50 dark:border-gray-850" />
<GroupModal bind:show={showDefaultPermissionsModal} tabs={['permissions']} custom={false} /> <GroupModal
bind:show={showDefaultPermissionsModal}
tabs={['permissions']}
custom={false}
onSubmit={updateDefaultPermissionsHandler}
/>
<button <button
class="flex items-center justify-between rounded-lg w-full transition pt-1" class="flex items-center justify-between rounded-lg w-full transition pt-1"

View File

@ -4,16 +4,9 @@
const i18n = getContext('i18n'); const i18n = getContext('i18n');
import Modal from '$lib/components/common/Modal.svelte'; import Modal from '$lib/components/common/Modal.svelte';
import Plus from '$lib/components/icons/Plus.svelte';
import Minus from '$lib/components/icons/Minus.svelte';
import PencilSolid from '$lib/components/icons/PencilSolid.svelte';
import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import Switch from '$lib/components/common/Switch.svelte';
import Display from './Display.svelte'; import Display from './Display.svelte';
import Permissions from './Permissions.svelte'; import Permissions from './Permissions.svelte';
import Users from './Users.svelte'; import Users from './Users.svelte';
import UsersSolid from '$lib/components/icons/UsersSolid.svelte';
import UserPlusSolid from '$lib/components/icons/UserPlusSolid.svelte'; import UserPlusSolid from '$lib/components/icons/UserPlusSolid.svelte';
import WrenchSolid from '$lib/components/icons/WrenchSolid.svelte'; import WrenchSolid from '$lib/components/icons/WrenchSolid.svelte';
@ -31,15 +24,25 @@
export let tabs = ['general', 'permissions', 'users']; export let tabs = ['general', 'permissions', 'users'];
let selectedTab = 'general'; let selectedTab = 'general';
let loading = false;
let name = ''; let name = '';
let description = ''; let description = '';
let permissions = {}; let permissions = {
workspace: {
models: false,
knowledge: false,
prompts: false,
tools: false
},
chat: {
delete: true,
edit: true,
temporary: true
}
};
let userIds = []; let userIds = [];
let adminIds = [];
let loading = false;
const submitHandler = async () => { const submitHandler = async () => {
loading = true; loading = true;
@ -65,7 +68,19 @@
if (group) { if (group) {
name = group.name; name = group.name;
description = group.description; description = group.description;
permissions = group?.permissions ?? {}; permissions = group?.permissions ?? {
workspace: {
models: false,
knowledge: false,
prompts: false,
tools: false
},
chat: {
delete: true,
edit: true,
temporary: true
}
};
userIds = group?.user_ids ?? []; userIds = group?.user_ids ?? [];
} }
}; };
@ -200,9 +215,9 @@
{#if selectedTab == 'general'} {#if selectedTab == 'general'}
<Display bind:name bind:description /> <Display bind:name bind:description />
{:else if selectedTab == 'permissions'} {:else if selectedTab == 'permissions'}
<Permissions bind:permissions {custom} /> <Permissions bind:permissions />
{:else if selectedTab == 'users'} {:else if selectedTab == 'users'}
<Users bind:userIds bind:adminIds {users} /> <Users bind:userIds {users} />
{/if} {/if}
</div> </div>
</div> </div>

View File

@ -1,7 +1,13 @@
<script> <script>
import { toast } from 'svelte-sonner';
import { getContext } from 'svelte';
const i18n = getContext('i18n');
import { deleteGroupById, updateGroupById } from '$lib/apis/groups';
import Pencil from '$lib/components/icons/Pencil.svelte'; import Pencil from '$lib/components/icons/Pencil.svelte';
import User from '$lib/components/icons/User.svelte'; import User from '$lib/components/icons/User.svelte';
import UserCircleSolid from '$lib/components/icons/UserCircleSolid.svelte'; import UserCircleSolid from '$lib/components/icons/UserCircleSolid.svelte';
import GroupModal from './EditGroupModal.svelte'; import GroupModal from './EditGroupModal.svelte';
@ -11,10 +17,43 @@
user_ids: [1, 2, 3] user_ids: [1, 2, 3]
}; };
export let setGroups = () => {};
let showEdit = false; let showEdit = false;
const updateHandler = async (_group) => {
const res = await updateGroupById(localStorage.token, group.id, _group).catch((error) => {
toast.error(error);
return null;
});
if (res) {
toast.success($i18n.t('Group updated successfully'));
setGroups();
}
};
const deleteHandler = async () => {
const res = await deleteGroupById(localStorage.token, group.id).catch((error) => {
toast.error(error);
return null;
});
if (res) {
toast.success($i18n.t('Group deleted successfully'));
setGroups();
}
};
</script> </script>
<GroupModal bind:show={showEdit} edit {group} {users} /> <GroupModal
bind:show={showEdit}
edit
{users}
{group}
onSubmit={updateHandler}
onDelete={deleteHandler}
/>
<div class="flex items-center gap-3 justify-between px-1 text-xs w-full transition"> <div class="flex items-center gap-3 justify-between px-1 text-xs w-full transition">
<div class="flex items-center gap-1.5 w-full font-medium"> <div class="flex items-center gap-1.5 w-full font-medium">

View File

@ -3,34 +3,25 @@
const i18n = getContext('i18n'); const i18n = getContext('i18n');
import Switch from '$lib/components/common/Switch.svelte'; import Switch from '$lib/components/common/Switch.svelte';
import { models } from '$lib/stores'; import Tooltip from '$lib/components/common/Tooltip.svelte';
import Minus from '$lib/components/icons/Minus.svelte';
import Plus from '$lib/components/icons/Plus.svelte';
export let permissions = {}; export let permissions = {
export let custom = true; workspace: {
models: false,
let defaultModelId = ''; knowledge: false,
prompts: false,
let selectedModelId = ''; tools: false
},
let filterEnabled = false; chat: {
let filterMode = 'include'; delete: true,
let filterModelIds = []; edit: true,
temporary: true
let workspaceModelsAccess = false; }
let workspaceKnowledgeAccess = false; };
let workspacePromptsAccess = false;
let workspaceToolsAccess = false;
let workspaceFunctionsAccess = false;
let chatDeletion = true;
let chatEdit = true;
let chatTemporary = true;
</script> </script>
<div> <div>
<div> <!-- <div>
<div class=" mb-2 text-sm font-medium">{$i18n.t('Model Permissions')}</div> <div class=" mb-2 text-sm font-medium">{$i18n.t('Model Permissions')}</div>
<div class="mb-2"> <div class="mb-2">
@ -130,52 +121,46 @@
</div> </div>
</div> </div>
<hr class=" border-gray-50 dark:border-gray-850 my-2" /> <hr class=" border-gray-50 dark:border-gray-850 my-2" /> -->
<div> <div>
<div class=" mb-2 text-sm font-medium">{$i18n.t('Workspace Permissions')}</div> <div class=" mb-2 text-sm font-medium">{$i18n.t('Workspace Permissions')}</div>
<div class=" flex w-full justify-between my-2 pr-2"> <div class=" flex w-full justify-between my-2 pr-2">
<div class=" self-center text-xs font-medium"> <div class=" self-center text-xs font-medium">
{#if custom}
{$i18n.t('Admins')}:
{/if}
{$i18n.t('Models Access')} {$i18n.t('Models Access')}
</div> </div>
<Switch bind:state={workspaceModelsAccess} /> <Switch bind:state={permissions.workspace.models} />
</div> </div>
<div class=" flex w-full justify-between my-2 pr-2"> <div class=" flex w-full justify-between my-2 pr-2">
<div class=" self-center text-xs font-medium"> <div class=" self-center text-xs font-medium">
{#if custom}
{$i18n.t('Admins')}:
{/if}
{$i18n.t('Knowledge Access')} {$i18n.t('Knowledge Access')}
</div> </div>
<Switch bind:state={workspaceKnowledgeAccess} /> <Switch bind:state={permissions.workspace.knowledge} />
</div> </div>
<div class=" flex w-full justify-between my-2 pr-2"> <div class=" flex w-full justify-between my-2 pr-2">
<div class=" self-center text-xs font-medium"> <div class=" self-center text-xs font-medium">
{#if custom}
{$i18n.t('Admins')}:
{/if}
{$i18n.t('Prompts Access')} {$i18n.t('Prompts Access')}
</div> </div>
<Switch bind:state={workspacePromptsAccess} /> <Switch bind:state={permissions.workspace.prompts} />
</div> </div>
<!-- <div class=" flex w-full justify-between my-2 pr-2"> <div class=" ">
<div class=" self-center text-xs font-medium">{$i18n.t('Allow Tools Access')}</div> <Tooltip
<Switch bind:state={workspaceToolsAccess} /> className=" flex w-full justify-between my-2 pr-2"
content={$i18n.t(
'Warning: Enabling this will allow users to upload arbitrary code on the server.'
)}
placement="top-start"
>
<div class=" self-center text-xs font-medium">
{$i18n.t('Tools Access')}
</div>
<Switch bind:state={permissions.workspace.tools} />
</Tooltip>
</div> </div>
<div class=" flex w-full justify-between my-2 pr-2">
<div class=" self-center text-xs font-medium">{$i18n.t('Allow Functions Access')}</div>
<Switch bind:state={workspaceFunctionsAccess} />
</div> -->
</div> </div>
<hr class=" border-gray-50 dark:border-gray-850 my-2" /> <hr class=" border-gray-50 dark:border-gray-850 my-2" />
@ -185,35 +170,26 @@
<div class=" flex w-full justify-between my-2 pr-2"> <div class=" flex w-full justify-between my-2 pr-2">
<div class=" self-center text-xs font-medium"> <div class=" self-center text-xs font-medium">
{#if custom} {$i18n.t('Allow Chat Delete')}
{$i18n.t('Members')}:
{/if}
{$i18n.t('Allow Chat Deletion')}
</div> </div>
<Switch bind:state={chatDeletion} /> <Switch bind:state={permissions.chat.delete} />
</div> </div>
<div class=" flex w-full justify-between my-2 pr-2"> <div class=" flex w-full justify-between my-2 pr-2">
<div class=" self-center text-xs font-medium"> <div class=" self-center text-xs font-medium">
{#if custom} {$i18n.t('Allow Chat Edit')}
{$i18n.t('Members')}:
{/if}
{$i18n.t('Allow Chat Editing')}
</div> </div>
<Switch bind:state={chatEdit} /> <Switch bind:state={permissions.chat.edit} />
</div> </div>
<div class=" flex w-full justify-between my-2 pr-2"> <div class=" flex w-full justify-between my-2 pr-2">
<div class=" self-center text-xs font-medium"> <div class=" self-center text-xs font-medium">
{#if custom}
{$i18n.t('Members')}:
{/if}
{$i18n.t('Allow Temporary Chat')} {$i18n.t('Allow Temporary Chat')}
</div> </div>
<Switch bind:state={chatTemporary} /> <Switch bind:state={permissions.chat.temporary} />
</div> </div>
</div> </div>
</div> </div>

View File

@ -10,7 +10,6 @@
export let users = []; export let users = [];
export let userIds = []; export let userIds = [];
export let adminIds = [];
let filteredUsers = []; let filteredUsers = [];
@ -30,16 +29,10 @@
); );
}) })
.sort((a, b) => { .sort((a, b) => {
const aIsAdmin = adminIds.includes(a.id);
const bIsAdmin = adminIds.includes(b.id);
const aUserIndex = userIds.indexOf(a.id); const aUserIndex = userIds.indexOf(a.id);
const bUserIndex = userIds.indexOf(b.id); const bUserIndex = userIds.indexOf(b.id);
// Admin users should come first // Compare based on userIds or fall back to alphabetical order
if (aIsAdmin && !bIsAdmin) return -1; // Place 'a' first if it's admin
if (!aIsAdmin && bIsAdmin) return 1; // Place 'b' first if it's admin
// Neither are admin, compare based on userIds or fall back to alphabetical order
if (aUserIndex !== -1 && bUserIndex === -1) return -1; // 'a' has valid userId -> prioritize if (aUserIndex !== -1 && bUserIndex === -1) return -1; // 'a' has valid userId -> prioritize
if (bUserIndex !== -1 && aUserIndex === -1) return 1; // 'b' has valid userId -> prioritize if (bUserIndex !== -1 && aUserIndex === -1) return 1; // 'b' has valid userId -> prioritize
@ -114,20 +107,7 @@
</Tooltip> </Tooltip>
{#if userIds.includes(user.id)} {#if userIds.includes(user.id)}
<button <Badge type="success" content="member" />
on:click={() => {
adminIds = adminIds.includes(user.id)
? adminIds.filter((id) => id !== user.id)
: [...adminIds, user.id];
}}
type="button"
>
{#if adminIds.includes(user.id)}
<Badge type="info" content="admin" />
{:else}
<Badge type="success" content="member" />
{/if}
</button>
{/if} {/if}
</div> </div>
</div> </div>