refac: access control

This commit is contained in:
Timothy Jaeryang Baek 2024-11-16 20:47:45 -08:00
parent b41e456c4f
commit 41bad9abcb
13 changed files with 209 additions and 73 deletions

View File

@ -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()),
}
)

View File

@ -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):

View File

@ -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

View File

@ -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) => {

View File

@ -0,0 +1,19 @@
<script lang="ts">
export let className = 'size-4';
export let strokeWidth = '1.5';
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width={strokeWidth}
stroke="currentColor"
class={className}
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M16.5 10.5V6.75a4.5 4.5 0 1 0-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 0 0 2.25-2.25v-6.75a2.25 2.25 0 0 0-2.25-2.25H6.75a2.25 2.25 0 0 0-2.25 2.25v6.75a2.25 2.25 0 0 0 2.25 2.25Z"
/>
</svg>

View File

@ -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 @@
</div>
<div class="mt-2">
<AccessControl />
<div class="px-3 py-2 bg-gray-50 dark:bg-gray-950 rounded-lg">
<AccessControl bind:accessControl />
</div>
</div>
<div class="flex justify-end mt-2">

View File

@ -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 @@
<div class="flex flex-col w-full h-full max-h-[100dvh] translate-y-1" id="collection-container">
{#if id && knowledge}
<AccessControlModal
bind:show={showAccessControlModal}
bind:accessControl={knowledge.access_control}
onChange={() => {
changeDebounceHandler();
}}
/>
<div class="w-full mb-2.5">
<div class=" flex w-full">
<div class="flex-1">
<div class="flex items-center justify-between w-full px-0.5 mb-1">
<div class="w-full">
<input
type="text"
class="text-left w-full font-semibold text-2xl font-primary bg-transparent outline-none"
bind:value={knowledge.name}
placeholder="Knowledge Name"
on:input={() => {
changeDebounceHandler();
}}
/>
</div>
<div class="self-center">
<button
class="bg-gray-50 hover:bg-gray-100 text-black transition px-2 py-1 rounded-full flex gap-1 items-center"
type="button"
on:click={() => {
showAccessControlModal = true;
}}
>
<LockClosed strokeWidth="2.5" />
<div class="text-sm font-medium flex-shrink-0">
{$i18n.t('Share')}
</div>
</button>
</div>
</div>
<div class="flex w-full px-1">
<input
type="text"
class="text-left text-xs w-full text-gray-500 bg-transparent outline-none"
bind:value={knowledge.description}
placeholder="Knowledge Description"
on:input={() => {
changeDebounceHandler();
}}
/>
</div>
</div>
</div>
</div>
<div class="flex flex-row flex-1 h-full max-h-full pb-2.5">
<PaneGroup direction="horizontal">
<Pane
@ -764,41 +821,7 @@
</div>
</div>
{:else}
<div class="m-auto pb-20">
<div>
<div class=" flex w-full mt-1 mb-3.5">
<div class="flex-1">
<div class="flex items-center justify-between w-full px-0.5 mb-1">
<div class="w-full">
<input
type="text"
class="text-center w-full font-medium text-3xl font-primary bg-transparent outline-none"
bind:value={knowledge.name}
on:input={() => {
changeDebounceHandler();
}}
/>
</div>
</div>
<div class="flex w-full px-1">
<input
type="text"
class="text-center w-full text-gray-500 bg-transparent outline-none"
bind:value={knowledge.description}
on:input={() => {
changeDebounceHandler();
}}
/>
</div>
</div>
</div>
</div>
<div class="mt-2">
<AccessControl />
</div>
</div>
<div></div>
{/if}
</div>
</Pane>

View File

@ -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:
};
</script>
<AccessControlModal bind:show={showAccessControlModal} bind:accessControl />
<div class=" flex flex-col justify-between w-full overflow-y-auto h-full">
<div class="mx-auto w-full md:px-0 h-full">
<form
@ -205,7 +214,7 @@ class Tools:
<div class="flex-1">
<Tooltip content={$i18n.t('e.g. My Tools')} placement="top-start">
<input
class="w-full text-2xl font-medium bg-transparent outline-none"
class="w-full text-2xl font-semibold bg-transparent outline-none"
type="text"
placeholder={$i18n.t('Tool Name')}
bind:value={name}
@ -215,7 +224,19 @@ class Tools:
</div>
<div>
<Badge type="muted" content={$i18n.t('Tool')} />
<button
class="bg-gray-50 hover:bg-gray-100 text-black transition px-2 py-1 rounded-full flex gap-1 items-center"
type="button"
on:click={() => {
showAccessControlModal = true;
}}
>
<LockClosed strokeWidth="2.5" />
<div class="text-sm font-medium flex-shrink-0">
{$i18n.t('Share')}
</div>
</button>
</div>
</div>

View File

@ -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);
</script>
<div class=" rounded-lg flex flex-col gap-2">

View File

@ -0,0 +1,43 @@
<script>
import { getContext } from 'svelte';
const i18n = getContext('i18n');
import Modal from '$lib/components/common/Modal.svelte';
import AccessControl from './AccessControl.svelte';
export let show = false;
export let accessControl = null;
export let onChange = () => {};
</script>
<Modal size="sm" bind:show>
<div>
<div class=" flex justify-between dark:text-gray-100 px-5 pt-3 pb-1">
<div class=" text-lg font-medium self-center font-primary">
{$i18n.t('Share')}
</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>
<div class="w-full px-5 pb-4">
<AccessControl bind:accessControl {onChange} />
</div>
</div>
</Modal>

View File

@ -36,7 +36,7 @@
? 'md:max-w-[calc(100%-260px)]'
: ''}"
>
<div class=" px-2.5 py-1 backdrop-blur-xl">
<div class=" px-2.5 pt-1 backdrop-blur-xl">
<div class=" flex items-center gap-1">
<div class="{$showSidebar ? 'md:hidden' : ''} self-center flex flex-none items-center">
<button
@ -97,7 +97,7 @@
</div>
</div>
<div class=" -mt-1 pb-1 px-[18px] flex-1 max-h-full overflow-y-auto" id="workspace-container">
<div class=" pb-1 px-[18px] flex-1 max-h-full overflow-y-auto" id="workspace-container">
<slot />
</div>
</div>

View File

@ -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);

View File

@ -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);
}}