From 41bad9abcbea33e5d54e3c8ed2aeefad92743a87 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sat, 16 Nov 2024 20:47:45 -0800 Subject: [PATCH] refac: access control --- .../open_webui/apps/webui/models/knowledge.py | 31 ++++-- backend/open_webui/apps/webui/models/tools.py | 2 + .../apps/webui/routers/knowledge.py | 24 ++--- src/lib/apis/knowledge/index.ts | 9 +- src/lib/components/icons/LockClosed.svelte | 19 ++++ .../Knowledge/CreateKnowledgeBase.svelte | 12 ++- .../workspace/Knowledge/KnowledgeBase.svelte | 99 ++++++++++++------- .../workspace/Tools/ToolkitEditor.svelte | 27 ++++- .../workspace/common/AccessControl.svelte | 4 + .../common/AccessControlModal.svelte | 43 ++++++++ src/routes/(app)/workspace/+layout.svelte | 4 +- .../(app)/workspace/tools/create/+page.svelte | 4 +- .../(app)/workspace/tools/edit/+page.svelte | 4 +- 13 files changed, 209 insertions(+), 73 deletions(-) create mode 100644 src/lib/components/icons/LockClosed.svelte create mode 100644 src/lib/components/workspace/common/AccessControlModal.svelte diff --git a/backend/open_webui/apps/webui/models/knowledge.py b/backend/open_webui/apps/webui/models/knowledge.py index 2ed7e1fe7..2d0e33f1b 100644 --- a/backend/open_webui/apps/webui/models/knowledge.py +++ b/backend/open_webui/apps/webui/models/knowledge.py @@ -85,6 +85,8 @@ class KnowledgeResponse(BaseModel): description: str data: Optional[dict] = None meta: Optional[dict] = None + + access_control: Optional[dict] = None created_at: int # timestamp in epoch updated_at: int # timestamp in epoch @@ -95,12 +97,7 @@ class KnowledgeForm(BaseModel): name: str description: str data: Optional[dict] = None - - -class KnowledgeUpdateForm(BaseModel): - name: Optional[str] = None - description: Optional[str] = None - data: Optional[dict] = None + access_control: Optional[dict] = None class KnowledgeTable: @@ -159,14 +156,32 @@ class KnowledgeTable: return None def update_knowledge_by_id( - self, id: str, form_data: KnowledgeUpdateForm, overwrite: bool = False + self, id: str, form_data: KnowledgeForm, overwrite: bool = False ) -> Optional[KnowledgeModel]: try: with get_db() as db: knowledge = self.get_knowledge_by_id(id=id) db.query(Knowledge).filter_by(id=id).update( { - **form_data.model_dump(exclude_none=True), + **form_data.model_dump(), + "updated_at": int(time.time()), + } + ) + db.commit() + return self.get_knowledge_by_id(id=id) + except Exception as e: + log.exception(e) + return None + + def update_knowledge_data_by_id( + self, id: str, data: dict + ) -> Optional[KnowledgeModel]: + try: + with get_db() as db: + knowledge = self.get_knowledge_by_id(id=id) + db.query(Knowledge).filter_by(id=id).update( + { + "data": data, "updated_at": int(time.time()), } ) diff --git a/backend/open_webui/apps/webui/models/tools.py b/backend/open_webui/apps/webui/models/tools.py index 1b7256df4..63570bee6 100644 --- a/backend/open_webui/apps/webui/models/tools.py +++ b/backend/open_webui/apps/webui/models/tools.py @@ -81,6 +81,7 @@ class ToolResponse(BaseModel): user_id: str name: str meta: ToolMeta + access_control: Optional[dict] = None updated_at: int # timestamp in epoch created_at: int # timestamp in epoch @@ -90,6 +91,7 @@ class ToolForm(BaseModel): name: str content: str meta: ToolMeta + access_control: Optional[dict] = None class ToolValves(BaseModel): diff --git a/backend/open_webui/apps/webui/routers/knowledge.py b/backend/open_webui/apps/webui/routers/knowledge.py index dfe233372..1ffadeac2 100644 --- a/backend/open_webui/apps/webui/routers/knowledge.py +++ b/backend/open_webui/apps/webui/routers/knowledge.py @@ -6,7 +6,6 @@ import logging from open_webui.apps.webui.models.knowledge import ( Knowledges, - KnowledgeUpdateForm, KnowledgeForm, KnowledgeResponse, ) @@ -64,8 +63,8 @@ async def get_knowledge(user=Depends(get_verified_user)): file_ids.remove(missing_file) data["file_ids"] = file_ids - Knowledges.update_knowledge_by_id( - id=knowledge_base.id, form_data=KnowledgeUpdateForm(data=data) + Knowledges.update_knowledge_data_by_id( + id=knowledge_base.id, data=data ) files = Files.get_file_metadatas_by_ids(file_ids) @@ -109,8 +108,8 @@ async def get_knowledge_list(user=Depends(get_verified_user)): file_ids.remove(missing_file) data["file_ids"] = file_ids - Knowledges.update_knowledge_by_id( - id=knowledge_base.id, form_data=KnowledgeUpdateForm(data=data) + Knowledges.update_knowledge_data_by_id( + id=knowledge_base.id, data=data ) files = Files.get_file_metadatas_by_ids(file_ids) @@ -186,7 +185,7 @@ async def get_knowledge_by_id(id: str, user=Depends(get_verified_user)): @router.post("/{id}/update", response_model=Optional[KnowledgeFilesResponse]) async def update_knowledge_by_id( id: str, - form_data: KnowledgeUpdateForm, + form_data: KnowledgeForm, user=Depends(get_verified_user), ): knowledge = Knowledges.get_knowledge_by_id(id=id) @@ -277,9 +276,7 @@ def add_file_to_knowledge_by_id( file_ids.append(form_data.file_id) data["file_ids"] = file_ids - knowledge = Knowledges.update_knowledge_by_id( - id=id, form_data=KnowledgeUpdateForm(data=data) - ) + knowledge = Knowledges.update_knowledge_data_by_id(id=id.id, data=data) if knowledge: files = Files.get_files_by_ids(file_ids) @@ -413,9 +410,7 @@ def remove_file_from_knowledge_by_id( file_ids.remove(form_data.file_id) data["file_ids"] = file_ids - knowledge = Knowledges.update_knowledge_by_id( - id=id, form_data=KnowledgeUpdateForm(data=data) - ) + knowledge = Knowledges.update_knowledge_data_by_id(id=id.id, data=data) if knowledge: files = Files.get_files_by_ids(file_ids) @@ -496,7 +491,6 @@ async def reset_knowledge_by_id(id: str, user=Depends(get_verified_user)): log.debug(e) pass - knowledge = Knowledges.update_knowledge_by_id( - id=id, form_data=KnowledgeUpdateForm(data={"file_ids": []}) - ) + knowledge = Knowledges.update_knowledge_data_by_id(id=id.id, data={"file_ids": []}) + return knowledge diff --git a/src/lib/apis/knowledge/index.ts b/src/lib/apis/knowledge/index.ts index c889e0594..da2b9d530 100644 --- a/src/lib/apis/knowledge/index.ts +++ b/src/lib/apis/knowledge/index.ts @@ -1,6 +1,6 @@ import { WEBUI_API_BASE_URL } from '$lib/constants'; -export const createNewKnowledge = async (token: string, name: string, description: string) => { +export const createNewKnowledge = async (token: string, name: string, description: string, accessControl: null|object) => { let error = null; const res = await fetch(`${WEBUI_API_BASE_URL}/knowledge/create`, { @@ -12,7 +12,8 @@ export const createNewKnowledge = async (token: string, name: string, descriptio }, body: JSON.stringify({ name: name, - description: description + description: description, + access_control: accessControl }) }) .then(async (res) => { @@ -130,6 +131,7 @@ type KnowledgeUpdateForm = { name?: string; description?: string; data?: object; + access_control?: null|object; }; export const updateKnowledgeById = async (token: string, id: string, form: KnowledgeUpdateForm) => { @@ -145,7 +147,8 @@ export const updateKnowledgeById = async (token: string, id: string, form: Knowl body: JSON.stringify({ name: form?.name ? form.name : undefined, description: form?.description ? form.description : undefined, - data: form?.data ? form.data : undefined + data: form?.data ? form.data : undefined, + access_control: form.access_control }) }) .then(async (res) => { diff --git a/src/lib/components/icons/LockClosed.svelte b/src/lib/components/icons/LockClosed.svelte new file mode 100644 index 000000000..c41171f4e --- /dev/null +++ b/src/lib/components/icons/LockClosed.svelte @@ -0,0 +1,19 @@ + + + + + diff --git a/src/lib/components/workspace/Knowledge/CreateKnowledgeBase.svelte b/src/lib/components/workspace/Knowledge/CreateKnowledgeBase.svelte index 29945cfb3..5d1e79808 100644 --- a/src/lib/components/workspace/Knowledge/CreateKnowledgeBase.svelte +++ b/src/lib/components/workspace/Knowledge/CreateKnowledgeBase.svelte @@ -12,6 +12,7 @@ let name = ''; let description = ''; + let accessControl = null; const submitHandler = async () => { loading = true; @@ -24,7 +25,12 @@ return; } - const res = await createNewKnowledge(localStorage.token, name, description).catch((e) => { + const res = await createNewKnowledge( + localStorage.token, + name, + description, + accessControl + ).catch((e) => { toast.error(e); }); @@ -105,7 +111,9 @@
- +
+ +
diff --git a/src/lib/components/workspace/Knowledge/KnowledgeBase.svelte b/src/lib/components/workspace/Knowledge/KnowledgeBase.svelte index f4e5b2a76..cd3bd55ca 100644 --- a/src/lib/components/workspace/Knowledge/KnowledgeBase.svelte +++ b/src/lib/components/workspace/Knowledge/KnowledgeBase.svelte @@ -38,8 +38,8 @@ import EllipsisVertical from '$lib/components/icons/EllipsisVertical.svelte'; import Drawer from '$lib/components/common/Drawer.svelte'; import ChevronLeft from '$lib/components/icons/ChevronLeft.svelte'; - import MenuLines from '$lib/components/icons/MenuLines.svelte'; - import AccessControl from '../common/AccessControl.svelte'; + import LockClosed from '$lib/components/icons/LockClosed.svelte'; + import AccessControlModal from '../common/AccessControlModal.svelte'; let largeScreen = true; @@ -63,6 +63,7 @@ let showAddTextContentModal = false; let showSyncConfirmModal = false; + let showAccessControlModal = false; let inputFiles = null; @@ -421,7 +422,8 @@ const res = await updateKnowledgeById(localStorage.token, id, { name: knowledge.name, - description: knowledge.description + description: knowledge.description, + access_control: knowledge.access_control }).catch((e) => { toast.error(e); }); @@ -599,6 +601,61 @@
{#if id && knowledge} + { + changeDebounceHandler(); + }} + /> +
+
+
+
+
+ { + changeDebounceHandler(); + }} + /> +
+ +
+ +
+
+ +
+ { + changeDebounceHandler(); + }} + /> +
+
+
+
+
{:else} -
-
-
-
-
-
- { - changeDebounceHandler(); - }} - /> -
-
- -
- { - changeDebounceHandler(); - }} - /> -
-
-
-
- -
- -
-
+
{/if}
diff --git a/src/lib/components/workspace/Tools/ToolkitEditor.svelte b/src/lib/components/workspace/Tools/ToolkitEditor.svelte index 9f5a9ea35..b435653f1 100644 --- a/src/lib/components/workspace/Tools/ToolkitEditor.svelte +++ b/src/lib/components/workspace/Tools/ToolkitEditor.svelte @@ -9,12 +9,16 @@ import Badge from '$lib/components/common/Badge.svelte'; import ChevronLeft from '$lib/components/icons/ChevronLeft.svelte'; import Tooltip from '$lib/components/common/Tooltip.svelte'; + import LockClosed from '$lib/components/icons/LockClosed.svelte'; + import AccessControlModal from '../common/AccessControlModal.svelte'; const dispatch = createEventDispatcher(); let formElement = null; let loading = false; + let showConfirm = false; + let showAccessControlModal = false; export let edit = false; export let clone = false; @@ -25,6 +29,8 @@ description: '' }; export let content = ''; + export let accessControl = null; + let _content = ''; $: if (content) { @@ -148,7 +154,8 @@ class Tools: id, name, meta, - content + content, + access_control: accessControl }); }; @@ -172,6 +179,8 @@ class Tools: }; + +
- +
diff --git a/src/lib/components/workspace/common/AccessControl.svelte b/src/lib/components/workspace/common/AccessControl.svelte index 8ddbe2b47..97ceb6d5a 100644 --- a/src/lib/components/workspace/common/AccessControl.svelte +++ b/src/lib/components/workspace/common/AccessControl.svelte @@ -9,6 +9,8 @@ import UserCircleSolid from '$lib/components/icons/UserCircleSolid.svelte'; import XMark from '$lib/components/icons/XMark.svelte'; + export let onChange: Function = () => {}; + export let accessControl = null; let selectedGroupId = ''; @@ -17,6 +19,8 @@ onMount(async () => { groups = await getGroups(localStorage.token); }); + + $: onChange(accessControl);
diff --git a/src/lib/components/workspace/common/AccessControlModal.svelte b/src/lib/components/workspace/common/AccessControlModal.svelte new file mode 100644 index 000000000..d0b846892 --- /dev/null +++ b/src/lib/components/workspace/common/AccessControlModal.svelte @@ -0,0 +1,43 @@ + + + +
+
+
+ {$i18n.t('Share')} +
+ +
+ +
+ +
+
+
diff --git a/src/routes/(app)/workspace/+layout.svelte b/src/routes/(app)/workspace/+layout.svelte index f4b51df2c..decce4fdd 100644 --- a/src/routes/(app)/workspace/+layout.svelte +++ b/src/routes/(app)/workspace/+layout.svelte @@ -36,7 +36,7 @@ ? 'md:max-w-[calc(100%-260px)]' : ''}" > -
+
-
+
diff --git a/src/routes/(app)/workspace/tools/create/+page.svelte b/src/routes/(app)/workspace/tools/create/+page.svelte index 29a285d22..5eeb261d1 100644 --- a/src/routes/(app)/workspace/tools/create/+page.svelte +++ b/src/routes/(app)/workspace/tools/create/+page.svelte @@ -36,7 +36,8 @@ id: data.id, name: data.name, meta: data.meta, - content: data.content + content: data.content, + access_control: data.access_control }).catch((error) => { toast.error(error); return null; @@ -86,6 +87,7 @@ name={tool?.name ?? ''} meta={tool?.meta ?? { description: '' }} content={tool?.content ?? ''} + access_control={null} {clone} on:save={(e) => { saveHandler(e.detail); diff --git a/src/routes/(app)/workspace/tools/edit/+page.svelte b/src/routes/(app)/workspace/tools/edit/+page.svelte index ebd47701e..ffda6dc12 100644 --- a/src/routes/(app)/workspace/tools/edit/+page.svelte +++ b/src/routes/(app)/workspace/tools/edit/+page.svelte @@ -36,7 +36,8 @@ id: data.id, name: data.name, meta: data.meta, - content: data.content + content: data.content, + access_control: data.access_control }).catch((error) => { toast.error(error); return null; @@ -73,6 +74,7 @@ name={tool.name} meta={tool.meta} content={tool.content} + accessControl={tool.access_control} on:save={(e) => { saveHandler(e.detail); }}