mirror of
https://github.com/open-webui/open-webui
synced 2024-11-25 13:29:53 +00:00
refac: access control
This commit is contained in:
parent
b41e456c4f
commit
41bad9abcb
@ -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()),
|
||||
}
|
||||
)
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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) => {
|
||||
|
19
src/lib/components/icons/LockClosed.svelte
Normal file
19
src/lib/components/icons/LockClosed.svelte
Normal 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>
|
@ -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">
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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">
|
||||
|
@ -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>
|
@ -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>
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}}
|
||||
|
Loading…
Reference in New Issue
Block a user