refac
This commit is contained in:
@@ -3,10 +3,11 @@ import { WEBUI_API_BASE_URL } from '$lib/constants';
|
|||||||
type ChannelForm = {
|
type ChannelForm = {
|
||||||
type?: string;
|
type?: string;
|
||||||
name: string;
|
name: string;
|
||||||
is_private?: boolean;
|
is_private?: boolean | null;
|
||||||
data?: object;
|
data?: object;
|
||||||
meta?: object;
|
meta?: object;
|
||||||
access_control?: object;
|
access_grants?: object[];
|
||||||
|
group_ids?: string[];
|
||||||
user_ids?: string[];
|
user_ids?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -99,6 +99,38 @@ export const getGroupById = async (token: string, id: string) => {
|
|||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getGroupInfoById = async (token: string, id: string) => {
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
const res = await fetch(`${WEBUI_API_BASE_URL}/groups/id/${id}/info`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
authorization: `Bearer ${token}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(async (res) => {
|
||||||
|
if (!res.ok) throw await res.json();
|
||||||
|
return res.json();
|
||||||
|
})
|
||||||
|
.then((json) => {
|
||||||
|
return json;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
error = err.detail;
|
||||||
|
|
||||||
|
console.error(err);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
export const updateGroupById = async (token: string, id: string, group: object) => {
|
export const updateGroupById = async (token: string, id: string, group: object) => {
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export const createNewKnowledge = async (
|
|||||||
token: string,
|
token: string,
|
||||||
name: string,
|
name: string,
|
||||||
description: string,
|
description: string,
|
||||||
accessControl: null | object
|
accessGrants: object[]
|
||||||
) => {
|
) => {
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ export const createNewKnowledge = async (
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
name: name,
|
name: name,
|
||||||
description: description,
|
description: description,
|
||||||
access_control: accessControl
|
access_grants: accessGrants
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.then(async (res) => {
|
.then(async (res) => {
|
||||||
@@ -248,7 +248,7 @@ type KnowledgeUpdateForm = {
|
|||||||
name?: string;
|
name?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
data?: object;
|
data?: object;
|
||||||
access_control?: null | object;
|
access_grants?: object[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateKnowledgeById = async (token: string, id: string, form: KnowledgeUpdateForm) => {
|
export const updateKnowledgeById = async (token: string, id: string, form: KnowledgeUpdateForm) => {
|
||||||
@@ -265,7 +265,7 @@ export const updateKnowledgeById = async (token: string, id: string, form: Knowl
|
|||||||
name: form?.name ? form.name : undefined,
|
name: form?.name ? form.name : undefined,
|
||||||
description: form?.description ? form.description : undefined,
|
description: form?.description ? form.description : undefined,
|
||||||
data: form?.data ? form.data : undefined,
|
data: form?.data ? form.data : undefined,
|
||||||
access_control: form.access_control
|
access_grants: form.access_grants
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.then(async (res) => {
|
.then(async (res) => {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ type NoteItem = {
|
|||||||
title: string;
|
title: string;
|
||||||
data: object;
|
data: object;
|
||||||
meta?: null | object;
|
meta?: null | object;
|
||||||
access_control?: null | object;
|
access_grants?: object[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createNewNote = async (token: string, note: NoteItem) => {
|
export const createNewNote = async (token: string, note: NoteItem) => {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ type PromptItem = {
|
|||||||
content: string;
|
content: string;
|
||||||
data?: object | null;
|
data?: object | null;
|
||||||
meta?: object | null;
|
meta?: object | null;
|
||||||
access_control?: null | object;
|
access_grants?: object[];
|
||||||
version_id?: string | null; // Active version
|
version_id?: string | null; // Active version
|
||||||
commit_message?: string | null; // For history tracking
|
commit_message?: string | null; // For history tracking
|
||||||
is_production?: boolean; // Whether to set new version as production
|
is_production?: boolean; // Whether to set new version as production
|
||||||
@@ -23,7 +23,7 @@ type PromptHistoryItem = {
|
|||||||
command: string;
|
command: string;
|
||||||
data: object;
|
data: object;
|
||||||
meta: object;
|
meta: object;
|
||||||
access_control: object | null;
|
access_grants: object[];
|
||||||
};
|
};
|
||||||
user_id: string;
|
user_id: string;
|
||||||
commit_message: string | null;
|
commit_message: string | null;
|
||||||
@@ -42,7 +42,7 @@ type PromptDiff = {
|
|||||||
to_snapshot: object;
|
to_snapshot: object;
|
||||||
content_diff: string[];
|
content_diff: string[];
|
||||||
name_changed: boolean;
|
name_changed: boolean;
|
||||||
access_control_changed: boolean;
|
access_grants_changed: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createNewPrompt = async (token: string, prompt: PromptItem) => {
|
export const createNewPrompt = async (token: string, prompt: PromptItem) => {
|
||||||
@@ -611,4 +611,3 @@ export const getPromptDiff = async (
|
|||||||
|
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -327,6 +327,33 @@ export const getUserById = async (token: string, userId: string) => {
|
|||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getUserInfoById = async (token: string, userId: string) => {
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
const res = await fetch(`${WEBUI_API_BASE_URL}/users/${userId}/info`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(async (res) => {
|
||||||
|
if (!res.ok) throw await res.json();
|
||||||
|
return res.json();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
error = err.detail;
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
export const updateUserStatus = async (token: string, formData: object) => {
|
export const updateUserStatus = async (token: string, formData: object) => {
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
|
|||||||
@@ -15,13 +15,32 @@
|
|||||||
import AddMembersModal from './ChannelInfoModal/AddMembersModal.svelte';
|
import AddMembersModal from './ChannelInfoModal/AddMembersModal.svelte';
|
||||||
|
|
||||||
export let show = false;
|
export let show = false;
|
||||||
export let channel = null;
|
export let channel: any = null;
|
||||||
|
|
||||||
export let onUpdate = () => {};
|
export let onUpdate = () => {};
|
||||||
|
|
||||||
let showAddMembersModal = false;
|
let showAddMembersModal = false;
|
||||||
const submitHandler = async () => {};
|
const submitHandler = async () => {};
|
||||||
|
|
||||||
|
const hasPublicReadGrant = (grants: any) =>
|
||||||
|
Array.isArray(grants) &&
|
||||||
|
grants.some(
|
||||||
|
(grant) =>
|
||||||
|
grant?.principal_type === 'user' &&
|
||||||
|
grant?.principal_id === '*' &&
|
||||||
|
grant?.permission === 'read'
|
||||||
|
);
|
||||||
|
|
||||||
|
const isPublicChannel = (channel: any): boolean => {
|
||||||
|
if (channel?.type === 'group') {
|
||||||
|
if (typeof channel?.is_private === 'boolean') {
|
||||||
|
return !channel.is_private;
|
||||||
|
}
|
||||||
|
return hasPublicReadGrant(channel?.access_grants);
|
||||||
|
}
|
||||||
|
return hasPublicReadGrant(channel?.access_grants);
|
||||||
|
};
|
||||||
|
|
||||||
const removeMemberHandler = async (userId) => {
|
const removeMemberHandler = async (userId) => {
|
||||||
const res = await removeMembersById(localStorage.token, channel.id, {
|
const res = await removeMembersById(localStorage.token, channel.id, {
|
||||||
user_ids: [userId]
|
user_ids: [userId]
|
||||||
@@ -62,7 +81,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class=" size-4 justify-center flex items-center">
|
<div class=" size-4 justify-center flex items-center">
|
||||||
{#if channel?.type === 'group' ? !channel?.is_private : channel?.access_control === null}
|
{#if isPublicChannel(channel)}
|
||||||
<Hashtag className="size-3.5" strokeWidth="2.5" />
|
<Hashtag className="size-3.5" strokeWidth="2.5" />
|
||||||
{:else}
|
{:else}
|
||||||
<Lock className="size-5.5" strokeWidth="2" />
|
<Lock className="size-5.5" strokeWidth="2" />
|
||||||
|
|||||||
@@ -129,6 +129,25 @@
|
|||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
window.removeEventListener('keydown', keydownListener);
|
window.removeEventListener('keydown', keydownListener);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const hasPublicReadGrant = (grants: any) =>
|
||||||
|
Array.isArray(grants) &&
|
||||||
|
grants.some(
|
||||||
|
(grant) =>
|
||||||
|
grant?.principal_type === 'user' &&
|
||||||
|
grant?.principal_id === '*' &&
|
||||||
|
grant?.permission === 'read'
|
||||||
|
);
|
||||||
|
|
||||||
|
const isPublicChannel = (channel: any): boolean => {
|
||||||
|
if (channel?.type === 'group') {
|
||||||
|
if (typeof channel?.is_private === 'boolean') {
|
||||||
|
return !channel.is_private;
|
||||||
|
}
|
||||||
|
return hasPublicReadGrant(channel?.access_grants);
|
||||||
|
}
|
||||||
|
return hasPublicReadGrant(channel?.access_grants);
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if filteredItems.length}
|
{#if filteredItems.length}
|
||||||
@@ -165,7 +184,7 @@
|
|||||||
>
|
>
|
||||||
{#if item.type === 'channel'}
|
{#if item.type === 'channel'}
|
||||||
<div class=" size-4 justify-center flex items-center mr-0.5">
|
<div class=" size-4 justify-center flex items-center mr-0.5">
|
||||||
{#if item?.data?.access_control === null}
|
{#if isPublicChannel(item?.data)}
|
||||||
<Hashtag className="size-3" strokeWidth="2.5" />
|
<Hashtag className="size-3" strokeWidth="2.5" />
|
||||||
{:else}
|
{:else}
|
||||||
<Lock className="size-[15px]" strokeWidth="2" />
|
<Lock className="size-[15px]" strokeWidth="2" />
|
||||||
|
|||||||
@@ -26,6 +26,25 @@
|
|||||||
let showChannelPinnedMessagesModal = false;
|
let showChannelPinnedMessagesModal = false;
|
||||||
let showChannelInfoModal = false;
|
let showChannelInfoModal = false;
|
||||||
|
|
||||||
|
const hasPublicReadGrant = (grants: any) =>
|
||||||
|
Array.isArray(grants) &&
|
||||||
|
grants.some(
|
||||||
|
(grant) =>
|
||||||
|
grant?.principal_type === 'user' &&
|
||||||
|
grant?.principal_id === '*' &&
|
||||||
|
grant?.permission === 'read'
|
||||||
|
);
|
||||||
|
|
||||||
|
const isPublicChannel = (channel: any): boolean => {
|
||||||
|
if (channel?.type === 'group') {
|
||||||
|
if (typeof channel?.is_private === 'boolean') {
|
||||||
|
return !channel.is_private;
|
||||||
|
}
|
||||||
|
return hasPublicReadGrant(channel?.access_grants);
|
||||||
|
}
|
||||||
|
return hasPublicReadGrant(channel?.access_grants);
|
||||||
|
};
|
||||||
|
|
||||||
export let channel;
|
export let channel;
|
||||||
|
|
||||||
export let onPin = (messageId, pinned) => {};
|
export let onPin = (messageId, pinned) => {};
|
||||||
@@ -112,7 +131,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
<div class=" size-4.5 justify-center flex items-center">
|
<div class=" size-4.5 justify-center flex items-center">
|
||||||
{#if channel?.type === 'group' ? !channel?.is_private : channel?.access_control === null}
|
{#if isPublicChannel(channel)}
|
||||||
<Hashtag className="size-3.5" strokeWidth="2.5" />
|
<Hashtag className="size-3.5" strokeWidth="2.5" />
|
||||||
{:else}
|
{:else}
|
||||||
<Lock className="size-5" strokeWidth="2" />
|
<Lock className="size-5" strokeWidth="2" />
|
||||||
|
|||||||
@@ -151,7 +151,7 @@
|
|||||||
return {
|
return {
|
||||||
...file,
|
...file,
|
||||||
user: undefined,
|
user: undefined,
|
||||||
access_control: undefined
|
access_grants: undefined
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
selectedToolIds,
|
selectedToolIds,
|
||||||
|
|||||||
@@ -560,7 +560,8 @@
|
|||||||
|
|
||||||
<ChannelModal
|
<ChannelModal
|
||||||
bind:show={showCreateChannel}
|
bind:show={showCreateChannel}
|
||||||
onSubmit={async ({ type, name, is_private, access_control, group_ids, user_ids }) => {
|
onSubmit={async (payload: any) => {
|
||||||
|
let { type, name, is_private, access_grants, group_ids, user_ids } = payload ?? {};
|
||||||
name = name?.trim();
|
name = name?.trim();
|
||||||
|
|
||||||
if (type === 'dm') {
|
if (type === 'dm') {
|
||||||
@@ -579,7 +580,7 @@
|
|||||||
type: type,
|
type: type,
|
||||||
name: name,
|
name: name,
|
||||||
is_private: is_private,
|
is_private: is_private,
|
||||||
access_control: access_control,
|
access_grants: access_grants,
|
||||||
group_ids: group_ids,
|
group_ids: group_ids,
|
||||||
user_ids: user_ids
|
user_ids: user_ids
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
|
|||||||
@@ -25,6 +25,25 @@
|
|||||||
let showEditChannelModal = false;
|
let showEditChannelModal = false;
|
||||||
|
|
||||||
let itemElement;
|
let itemElement;
|
||||||
|
|
||||||
|
const hasPublicReadGrant = (grants: any) =>
|
||||||
|
Array.isArray(grants) &&
|
||||||
|
grants.some(
|
||||||
|
(grant) =>
|
||||||
|
grant?.principal_type === 'user' &&
|
||||||
|
grant?.principal_id === '*' &&
|
||||||
|
grant?.permission === 'read'
|
||||||
|
);
|
||||||
|
|
||||||
|
const isPublicChannel = (channel: any): boolean => {
|
||||||
|
if (channel?.type === 'group') {
|
||||||
|
if (typeof channel?.is_private === 'boolean') {
|
||||||
|
return !channel.is_private;
|
||||||
|
}
|
||||||
|
return hasPublicReadGrant(channel?.access_grants);
|
||||||
|
}
|
||||||
|
return hasPublicReadGrant(channel?.access_grants);
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ChannelModal
|
<ChannelModal
|
||||||
@@ -32,11 +51,12 @@
|
|||||||
{channel}
|
{channel}
|
||||||
edit={true}
|
edit={true}
|
||||||
{onUpdate}
|
{onUpdate}
|
||||||
onSubmit={async ({ name, is_private, access_control, group_ids, user_ids }) => {
|
onSubmit={async (payload: any) => {
|
||||||
|
const { name, is_private, access_grants, group_ids, user_ids } = payload ?? {};
|
||||||
const res = await updateChannelById(localStorage.token, channel.id, {
|
const res = await updateChannelById(localStorage.token, channel.id, {
|
||||||
name,
|
name,
|
||||||
is_private,
|
is_private,
|
||||||
access_control,
|
access_grants,
|
||||||
group_ids,
|
group_ids,
|
||||||
user_ids
|
user_ids
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
@@ -123,7 +143,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
<div class=" size-4 justify-center flex items-center ml-1">
|
<div class=" size-4 justify-center flex items-center ml-1">
|
||||||
{#if channel?.type === 'group' ? !channel?.is_private : channel?.access_control === null}
|
{#if isPublicChannel(channel)}
|
||||||
<Hashtag className="size-3.5" strokeWidth="2.5" />
|
<Hashtag className="size-3.5" strokeWidth="2.5" />
|
||||||
{:else}
|
{:else}
|
||||||
<Lock className="size-[15px]" strokeWidth="2" />
|
<Lock className="size-[15px]" strokeWidth="2" />
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
export let onSubmit: Function = () => {};
|
export let onSubmit: Function = () => {};
|
||||||
export let onUpdate: Function = () => {};
|
export let onUpdate: Function = () => {};
|
||||||
|
|
||||||
export let channel = null;
|
export let channel: any = null;
|
||||||
export let edit = false;
|
export let edit = false;
|
||||||
|
|
||||||
let channelTypes = ['group', 'dm'];
|
let channelTypes = ['group', 'dm'];
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
let name = '';
|
let name = '';
|
||||||
|
|
||||||
let isPrivate = null;
|
let isPrivate = null;
|
||||||
let accessControl = {};
|
let accessGrants = [];
|
||||||
|
|
||||||
let groupIds = [];
|
let groupIds = [];
|
||||||
let userIds = [];
|
let userIds = [];
|
||||||
@@ -65,8 +65,8 @@
|
|||||||
await onSubmit({
|
await onSubmit({
|
||||||
type: type,
|
type: type,
|
||||||
name: name.replace(/\s/g, '-'),
|
name: name.replace(/\s/g, '-'),
|
||||||
is_private: type === 'group' ? isPrivate : null,
|
is_private: type === 'group' ? (isPrivate ?? true) : null,
|
||||||
access_control: type === '' ? accessControl : {},
|
access_grants: type === '' ? accessGrants : [],
|
||||||
group_ids: groupIds,
|
group_ids: groupIds,
|
||||||
user_ids: userIds
|
user_ids: userIds
|
||||||
});
|
});
|
||||||
@@ -85,8 +85,12 @@
|
|||||||
|
|
||||||
if (channel) {
|
if (channel) {
|
||||||
name = channel?.name ?? '';
|
name = channel?.name ?? '';
|
||||||
isPrivate = channel?.is_private ?? null;
|
if (type === 'group') {
|
||||||
accessControl = channel.access_control;
|
isPrivate = typeof channel?.is_private === 'boolean' ? channel.is_private : true;
|
||||||
|
} else {
|
||||||
|
isPrivate = null;
|
||||||
|
}
|
||||||
|
accessGrants = channel?.access_grants ?? [];
|
||||||
userIds = channel?.user_ids ?? [];
|
userIds = channel?.user_ids ?? [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -102,8 +106,14 @@
|
|||||||
|
|
||||||
const deleteHandler = async () => {
|
const deleteHandler = async () => {
|
||||||
showDeleteConfirmDialog = false;
|
showDeleteConfirmDialog = false;
|
||||||
|
if (!channel?.id) {
|
||||||
|
show = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const res = await deleteChannelById(localStorage.token, channel.id).catch((error) => {
|
const channelId = channel.id;
|
||||||
|
|
||||||
|
const res = await deleteChannelById(localStorage.token, channelId).catch((error) => {
|
||||||
toast.error(error.message);
|
toast.error(error.message);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -111,7 +121,7 @@
|
|||||||
toast.success($i18n.t('Channel deleted successfully'));
|
toast.success($i18n.t('Channel deleted successfully'));
|
||||||
onUpdate();
|
onUpdate();
|
||||||
|
|
||||||
if ($page.url.pathname === `/channels/${channel.id}`) {
|
if ($page.url.pathname === `/channels/${channelId}`) {
|
||||||
goto('/');
|
goto('/');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,7 +132,7 @@
|
|||||||
const resetHandler = () => {
|
const resetHandler = () => {
|
||||||
type = '';
|
type = '';
|
||||||
name = '';
|
name = '';
|
||||||
accessControl = {};
|
accessGrants = [];
|
||||||
userIds = [];
|
userIds = [];
|
||||||
loading = false;
|
loading = false;
|
||||||
};
|
};
|
||||||
@@ -226,11 +236,11 @@
|
|||||||
{#if type !== 'dm'}
|
{#if type !== 'dm'}
|
||||||
<div class="-mx-2 mb-1 mt-2.5 px-2">
|
<div class="-mx-2 mb-1 mt-2.5 px-2">
|
||||||
{#if type === ''}
|
{#if type === ''}
|
||||||
<AccessControl bind:accessControl accessRoles={['read', 'write']} />
|
<AccessControl bind:accessGrants accessRoles={['read', 'write']} />
|
||||||
{:else if type === 'group'}
|
{:else if type === 'group'}
|
||||||
<Visibility
|
<Visibility
|
||||||
state={isPrivate ? 'private' : 'public'}
|
state={isPrivate ? 'private' : 'public'}
|
||||||
onChange={(value) => {
|
onChange={(value: string) => {
|
||||||
if (value === 'private') {
|
if (value === 'private') {
|
||||||
isPrivate = true;
|
isPrivate = true;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -108,9 +108,18 @@
|
|||||||
},
|
},
|
||||||
// pages: [], // TODO: Implement pages for notes to allow users to create multiple pages in a note
|
// pages: [], // TODO: Implement pages for notes to allow users to create multiple pages in a note
|
||||||
meta: null,
|
meta: null,
|
||||||
access_control: {}
|
access_grants: []
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const hasPublicReadGrant = (grants) =>
|
||||||
|
Array.isArray(grants) &&
|
||||||
|
grants.some(
|
||||||
|
(grant) =>
|
||||||
|
grant?.principal_type === 'user' &&
|
||||||
|
grant?.principal_id === '*' &&
|
||||||
|
grant?.permission === 'read'
|
||||||
|
);
|
||||||
|
|
||||||
let files = [];
|
let files = [];
|
||||||
let messages = [];
|
let messages = [];
|
||||||
|
|
||||||
@@ -161,6 +170,9 @@
|
|||||||
|
|
||||||
if (res) {
|
if (res) {
|
||||||
note = res;
|
note = res;
|
||||||
|
if (!Array.isArray(note?.access_grants)) {
|
||||||
|
note.access_grants = [];
|
||||||
|
}
|
||||||
files = res.data.files || [];
|
files = res.data.files || [];
|
||||||
|
|
||||||
if (note?.write_access) {
|
if (note?.write_access) {
|
||||||
@@ -193,7 +205,7 @@
|
|||||||
data: {
|
data: {
|
||||||
files: files
|
files: files
|
||||||
},
|
},
|
||||||
access_control: note?.access_control
|
access_grants: note?.access_grants ?? []
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
toast.error(`${e}`);
|
toast.error(`${e}`);
|
||||||
});
|
});
|
||||||
@@ -765,8 +777,8 @@ Provide the enhanced notes in markdown format. Use markdown syntax for headings,
|
|||||||
console.log('noteEventHandler', _note);
|
console.log('noteEventHandler', _note);
|
||||||
if (_note.id !== id) return;
|
if (_note.id !== id) return;
|
||||||
|
|
||||||
if (_note.access_control && _note.access_control !== note.access_control) {
|
if (_note.access_grants && _note.access_grants !== note.access_grants) {
|
||||||
note.access_control = _note.access_control;
|
note.access_grants = _note.access_grants;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_note.data && _note.data.files) {
|
if (_note.data && _note.data.files) {
|
||||||
@@ -851,7 +863,7 @@ Provide the enhanced notes in markdown format. Use markdown syntax for headings,
|
|||||||
{#if note}
|
{#if note}
|
||||||
<AccessControlModal
|
<AccessControlModal
|
||||||
bind:show={showAccessControlModal}
|
bind:show={showAccessControlModal}
|
||||||
bind:accessControl={note.access_control}
|
bind:accessGrants={note.access_grants}
|
||||||
accessRoles={['read', 'write']}
|
accessRoles={['read', 'write']}
|
||||||
onChange={() => {
|
onChange={() => {
|
||||||
changeDebounceHandler();
|
changeDebounceHandler();
|
||||||
@@ -1114,7 +1126,11 @@ Provide the enhanced notes in markdown format. Use markdown syntax for headings,
|
|||||||
}}
|
}}
|
||||||
disabled={note?.user_id !== $user?.id && $user?.role !== 'admin'}
|
disabled={note?.user_id !== $user?.id && $user?.role !== 'admin'}
|
||||||
>
|
>
|
||||||
<span> {note?.access_control ? $i18n.t('Private') : $i18n.t('Everyone')} </span>
|
<span>
|
||||||
|
{hasPublicReadGrant(note?.access_grants)
|
||||||
|
? $i18n.t('Everyone')
|
||||||
|
: $i18n.t('Private')}
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
{:else}
|
{:else}
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -129,7 +129,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
meta: null,
|
meta: null,
|
||||||
access_control: {}
|
access_grants: []
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
toast.error(`${error}`);
|
toast.error(`${error}`);
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ export const createNoteHandler = async (title: string, md?: string, html?: strin
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
meta: null,
|
meta: null,
|
||||||
access_control: {}
|
access_grants: []
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
toast.error(`${error}`);
|
toast.error(`${error}`);
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
let name = '';
|
let name = '';
|
||||||
let description = '';
|
let description = '';
|
||||||
let accessControl = {};
|
let accessGrants = [];
|
||||||
|
|
||||||
const submitHandler = async () => {
|
const submitHandler = async () => {
|
||||||
loading = true;
|
loading = true;
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
localStorage.token,
|
localStorage.token,
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
accessControl
|
accessGrants
|
||||||
).catch((e) => {
|
).catch((e) => {
|
||||||
toast.error(`${e}`);
|
toast.error(`${e}`);
|
||||||
});
|
});
|
||||||
@@ -114,7 +114,7 @@
|
|||||||
|
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<AccessControl
|
<AccessControl
|
||||||
bind:accessControl
|
bind:accessGrants
|
||||||
accessRoles={['read', 'write']}
|
accessRoles={['read', 'write']}
|
||||||
share={$user?.permissions?.sharing?.knowledge || $user?.role === 'admin'}
|
share={$user?.permissions?.sharing?.knowledge || $user?.role === 'admin'}
|
||||||
sharePublic={$user?.permissions?.sharing?.public_knowledge || $user?.role === 'admin'}
|
sharePublic={$user?.permissions?.sharing?.public_knowledge || $user?.role === 'admin'}
|
||||||
|
|||||||
@@ -75,6 +75,8 @@
|
|||||||
file_ids: string[];
|
file_ids: string[];
|
||||||
};
|
};
|
||||||
files: any[];
|
files: any[];
|
||||||
|
access_grants?: any[];
|
||||||
|
write_access?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
let id = null;
|
let id = null;
|
||||||
@@ -614,7 +616,7 @@
|
|||||||
...knowledge,
|
...knowledge,
|
||||||
name: knowledge.name,
|
name: knowledge.name,
|
||||||
description: knowledge.description,
|
description: knowledge.description,
|
||||||
access_control: knowledge.access_control
|
access_grants: knowledge.access_grants ?? []
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
toast.error(`${e}`);
|
toast.error(`${e}`);
|
||||||
});
|
});
|
||||||
@@ -745,6 +747,9 @@
|
|||||||
|
|
||||||
if (res) {
|
if (res) {
|
||||||
knowledge = res;
|
knowledge = res;
|
||||||
|
if (!Array.isArray(knowledge?.access_grants)) {
|
||||||
|
knowledge.access_grants = [];
|
||||||
|
}
|
||||||
knowledgeId = knowledge?.id;
|
knowledgeId = knowledge?.id;
|
||||||
} else {
|
} else {
|
||||||
goto('/workspace/knowledge');
|
goto('/workspace/knowledge');
|
||||||
@@ -828,9 +833,11 @@
|
|||||||
{#if id && knowledge}
|
{#if id && knowledge}
|
||||||
<AccessControlModal
|
<AccessControlModal
|
||||||
bind:show={showAccessControlModal}
|
bind:show={showAccessControlModal}
|
||||||
bind:accessControl={knowledge.access_control}
|
bind:accessGrants={knowledge.access_grants}
|
||||||
share={$user?.permissions?.sharing?.knowledge || $user?.role === 'admin'}
|
share={$user?.permissions?.sharing?.knowledge || $user?.role === 'admin'}
|
||||||
sharePublic={$user?.permissions?.sharing?.public_knowledge || $user?.role === 'admin'}
|
sharePublic={$user?.permissions?.sharing?.public_knowledge ||
|
||||||
|
$user?.role === 'admin' ||
|
||||||
|
knowledge?.write_access}
|
||||||
onChange={() => {
|
onChange={() => {
|
||||||
changeDebounceHandler();
|
changeDebounceHandler();
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -108,7 +108,7 @@
|
|||||||
let builtinTools = {};
|
let builtinTools = {};
|
||||||
|
|
||||||
let actionIds = [];
|
let actionIds = [];
|
||||||
let accessControl = {};
|
let accessGrants = [];
|
||||||
let tts = { voice: '' };
|
let tts = { voice: '' };
|
||||||
|
|
||||||
const submitHandler = async () => {
|
const submitHandler = async () => {
|
||||||
@@ -140,7 +140,7 @@
|
|||||||
|
|
||||||
info.params = { ...info.params, ...params };
|
info.params = { ...info.params, ...params };
|
||||||
|
|
||||||
info.access_control = accessControl;
|
info.access_grants = accessGrants;
|
||||||
info.meta.capabilities = capabilities;
|
info.meta.capabilities = capabilities;
|
||||||
|
|
||||||
if (enableDescription) {
|
if (enableDescription) {
|
||||||
@@ -301,14 +301,7 @@
|
|||||||
builtinTools = model?.meta?.builtinTools ?? {};
|
builtinTools = model?.meta?.builtinTools ?? {};
|
||||||
tts = { voice: model?.meta?.tts?.voice ?? '' };
|
tts = { voice: model?.meta?.tts?.voice ?? '' };
|
||||||
|
|
||||||
if ('access_control' in model) {
|
accessGrants = model?.access_grants ?? [];
|
||||||
accessControl = model.access_control;
|
|
||||||
} else {
|
|
||||||
accessControl = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(model?.access_control);
|
|
||||||
console.log(accessControl);
|
|
||||||
|
|
||||||
info = {
|
info = {
|
||||||
...info,
|
...info,
|
||||||
@@ -334,10 +327,10 @@
|
|||||||
{#if loaded}
|
{#if loaded}
|
||||||
<AccessControlModal
|
<AccessControlModal
|
||||||
bind:show={showAccessControlModal}
|
bind:show={showAccessControlModal}
|
||||||
bind:accessControl
|
bind:accessGrants
|
||||||
accessRoles={['read', 'write']}
|
accessRoles={['read', 'write']}
|
||||||
share={$user?.permissions?.sharing?.models || $user?.role === 'admin'}
|
share={$user?.permissions?.sharing?.models || $user?.role === 'admin'}
|
||||||
sharePublic={$user?.permissions?.sharing?.public_models || $user?.role === 'admin'}
|
sharePublic={$user?.permissions?.sharing?.public_models || $user?.role === 'admin' || edit}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{#if onBack}
|
{#if onBack}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
let commitMessage = '';
|
let commitMessage = '';
|
||||||
let isProduction = true;
|
let isProduction = true;
|
||||||
|
|
||||||
let accessControl = {};
|
let accessGrants = [];
|
||||||
let showAccessControlModal = false;
|
let showAccessControlModal = false;
|
||||||
let hasManualEdit = false;
|
let hasManualEdit = false;
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
command,
|
command,
|
||||||
content,
|
content,
|
||||||
tags: tags.map((tag) => tag.name),
|
tags: tags.map((tag) => tag.name),
|
||||||
access_control: accessControl,
|
access_grants: accessGrants,
|
||||||
commit_message: commitMessage || undefined,
|
commit_message: commitMessage || undefined,
|
||||||
is_production: isProduction
|
is_production: isProduction
|
||||||
});
|
});
|
||||||
@@ -259,7 +259,7 @@
|
|||||||
command = prompt.command.at(0) === '/' ? prompt.command.slice(1) : prompt.command;
|
command = prompt.command.at(0) === '/' ? prompt.command.slice(1) : prompt.command;
|
||||||
content = prompt.content;
|
content = prompt.content;
|
||||||
tags = (prompt.tags || []).map((tag) => ({ name: tag }));
|
tags = (prompt.tags || []).map((tag) => ({ name: tag }));
|
||||||
accessControl = prompt?.access_control === undefined ? {} : prompt?.access_control;
|
accessGrants = prompt?.access_grants === undefined ? [] : prompt?.access_grants;
|
||||||
|
|
||||||
// Store originals for revert on collision
|
// Store originals for revert on collision
|
||||||
originalName = name;
|
originalName = name;
|
||||||
@@ -286,10 +286,10 @@
|
|||||||
|
|
||||||
<AccessControlModal
|
<AccessControlModal
|
||||||
bind:show={showAccessControlModal}
|
bind:show={showAccessControlModal}
|
||||||
bind:accessControl
|
bind:accessGrants
|
||||||
accessRoles={['read', 'write']}
|
accessRoles={['read', 'write']}
|
||||||
share={$user?.permissions?.sharing?.prompts || $user?.role === 'admin'}
|
share={$user?.permissions?.sharing?.prompts || $user?.role === 'admin'}
|
||||||
sharePublic={$user?.permissions?.sharing?.public_prompts || $user?.role === 'admin'}
|
sharePublic={$user?.permissions?.sharing?.public_prompts || $user?.role === 'admin' || edit}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Edit Modal -->
|
<!-- Edit Modal -->
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
description: ''
|
description: ''
|
||||||
};
|
};
|
||||||
export let content = '';
|
export let content = '';
|
||||||
export let accessControl = {};
|
export let accessGrants = [];
|
||||||
|
|
||||||
let _content = '';
|
let _content = '';
|
||||||
|
|
||||||
@@ -161,7 +161,7 @@ class Tools:
|
|||||||
name,
|
name,
|
||||||
meta,
|
meta,
|
||||||
content,
|
content,
|
||||||
access_control: accessControl
|
access_grants: accessGrants
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -187,10 +187,10 @@ class Tools:
|
|||||||
|
|
||||||
<AccessControlModal
|
<AccessControlModal
|
||||||
bind:show={showAccessControlModal}
|
bind:show={showAccessControlModal}
|
||||||
bind:accessControl
|
bind:accessGrants
|
||||||
accessRoles={['read', 'write']}
|
accessRoles={['read', 'write']}
|
||||||
share={$user?.permissions?.sharing?.tools || $user?.role === 'admin'}
|
share={$user?.permissions?.sharing?.tools || $user?.role === 'admin'}
|
||||||
sharePublic={$user?.permissions?.sharing?.public_tools || $user?.role === 'admin'}
|
sharePublic={$user?.permissions?.sharing?.public_tools || $user?.role === 'admin' || edit}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class=" flex flex-col justify-between w-full overflow-y-auto h-full">
|
<div class=" flex flex-col justify-between w-full overflow-y-auto h-full">
|
||||||
|
|||||||
@@ -3,75 +3,430 @@
|
|||||||
|
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
import { getGroups } from '$lib/apis/groups';
|
import { getGroups, getGroupById, getGroupInfoById } from '$lib/apis/groups';
|
||||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
import { getUserById, getUserInfoById } from '$lib/apis/users';
|
||||||
import Plus from '$lib/components/icons/Plus.svelte';
|
import { WEBUI_API_BASE_URL } from '$lib/constants';
|
||||||
import UserCircleSolid from '$lib/components/icons/UserCircleSolid.svelte';
|
|
||||||
import XMark from '$lib/components/icons/XMark.svelte';
|
import XMark from '$lib/components/icons/XMark.svelte';
|
||||||
import Badge from '$lib/components/common/Badge.svelte';
|
import Badge from '$lib/components/common/Badge.svelte';
|
||||||
|
import GlobeAlt from '$lib/components/icons/GlobeAlt.svelte';
|
||||||
|
import Plus from '$lib/components/icons/Plus.svelte';
|
||||||
|
import AddAccessModal from './AddAccessModal.svelte';
|
||||||
|
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||||
|
|
||||||
|
type AccessGrant = {
|
||||||
|
id?: string;
|
||||||
|
principal_type: 'user' | 'group';
|
||||||
|
principal_id: string;
|
||||||
|
permission: 'read' | 'write';
|
||||||
|
};
|
||||||
|
|
||||||
|
type LegacyAccessControl = {
|
||||||
|
read: { group_ids: string[]; user_ids: string[] };
|
||||||
|
write: { group_ids: string[]; user_ids: string[] };
|
||||||
|
};
|
||||||
|
|
||||||
export let onChange: Function = () => {};
|
export let onChange: Function = () => {};
|
||||||
|
|
||||||
export let accessRoles = ['read'];
|
export let accessRoles = ['read'];
|
||||||
export let accessControl = {};
|
export let accessGrants: AccessGrant[] | any = [];
|
||||||
|
export let accessControl: any = undefined;
|
||||||
|
|
||||||
export let share = true;
|
export let share = true;
|
||||||
export let sharePublic = true;
|
export let sharePublic = true;
|
||||||
|
|
||||||
let selectedGroupId = '';
|
let groups: any[] = [];
|
||||||
let groups = [];
|
const resolvingGroupIds = new Set<string>();
|
||||||
|
let userById: Record<string, any> = {};
|
||||||
|
const resolvingUserIds = new Set<string>();
|
||||||
|
|
||||||
$: if (!sharePublic && accessControl === null) {
|
let showAddAccessModal = false;
|
||||||
initPublicAccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
const initPublicAccess = () => {
|
const dedupeAccessGrants = (grants: AccessGrant[] | null | undefined): AccessGrant[] => {
|
||||||
if (!sharePublic && accessControl === null) {
|
if (!Array.isArray(grants)) return [];
|
||||||
accessControl = {
|
const map = new Map<string, AccessGrant>();
|
||||||
read: {
|
for (const grant of grants) {
|
||||||
group_ids: [],
|
if (!grant) continue;
|
||||||
user_ids: []
|
const key = `${grant.principal_type}:${grant.principal_id}:${grant.permission}`;
|
||||||
},
|
if (!grant.principal_type || !grant.principal_id || !grant.permission) continue;
|
||||||
write: {
|
map.set(key, {
|
||||||
group_ids: [],
|
id: grant.id,
|
||||||
user_ids: []
|
principal_type: grant.principal_type,
|
||||||
|
principal_id: grant.principal_id,
|
||||||
|
permission: grant.permission
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Array.from(map.values());
|
||||||
|
};
|
||||||
|
|
||||||
|
const legacyAccessControlToGrants = (accessControl: any): AccessGrant[] => {
|
||||||
|
if (accessControl === null) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
principal_type: 'user',
|
||||||
|
principal_id: '*',
|
||||||
|
permission: 'read'
|
||||||
}
|
}
|
||||||
};
|
];
|
||||||
onChange(accessControl);
|
}
|
||||||
|
|
||||||
|
if (!accessControl || typeof accessControl !== 'object') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const grants: AccessGrant[] = [];
|
||||||
|
for (const permission of ['read', 'write'] as const) {
|
||||||
|
const entry = accessControl?.[permission] ?? {};
|
||||||
|
for (const groupId of entry?.group_ids ?? []) {
|
||||||
|
grants.push({
|
||||||
|
principal_type: 'group',
|
||||||
|
principal_id: groupId,
|
||||||
|
permission
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (const userId of entry?.user_ids ?? []) {
|
||||||
|
grants.push({
|
||||||
|
principal_type: 'user',
|
||||||
|
principal_id: userId,
|
||||||
|
permission
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dedupeAccessGrants(grants);
|
||||||
|
};
|
||||||
|
|
||||||
|
const grantsToLegacyAccessControl = (grants: AccessGrant[]): null | LegacyAccessControl => {
|
||||||
|
const normalized = dedupeAccessGrants(grants);
|
||||||
|
if (hasPublicReadGrant(normalized)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: LegacyAccessControl = {
|
||||||
|
read: { group_ids: [], user_ids: [] },
|
||||||
|
write: { group_ids: [], user_ids: [] }
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const grant of normalized) {
|
||||||
|
if (!['read', 'write'].includes(grant.permission)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (grant.principal_type === 'group') {
|
||||||
|
if (!result[grant.permission].group_ids.includes(grant.principal_id)) {
|
||||||
|
result[grant.permission].group_ids = [
|
||||||
|
...result[grant.permission].group_ids,
|
||||||
|
grant.principal_id
|
||||||
|
];
|
||||||
|
}
|
||||||
|
} else if (grant.principal_type === 'user' && grant.principal_id !== '*') {
|
||||||
|
if (!result[grant.permission].user_ids.includes(grant.principal_id)) {
|
||||||
|
result[grant.permission].user_ids = [
|
||||||
|
...result[grant.permission].user_ids,
|
||||||
|
grant.principal_id
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalizeInputToGrants = (value: any): AccessGrant[] => {
|
||||||
|
if (value === null) {
|
||||||
|
return legacyAccessControlToGrants(null);
|
||||||
|
}
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return dedupeAccessGrants(value);
|
||||||
|
}
|
||||||
|
if (value && typeof value === 'object' && ('read' in value || 'write' in value)) {
|
||||||
|
return legacyAccessControlToGrants(value);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const stableStringify = (value: any): string => {
|
||||||
|
try {
|
||||||
|
return JSON.stringify(value ?? null);
|
||||||
|
} catch {
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const hasPublicReadGrant = (grants: AccessGrant[]): boolean =>
|
||||||
|
grants.some(
|
||||||
|
(grant) =>
|
||||||
|
grant.principal_type === 'user' && grant.principal_id === '*' && grant.permission === 'read'
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentGrants = (): AccessGrant[] =>
|
||||||
|
Array.isArray(accessGrants) ? (accessGrants as AccessGrant[]) : [];
|
||||||
|
|
||||||
|
const getPrincipalIdsByPermission = (
|
||||||
|
principalType: 'user' | 'group',
|
||||||
|
permission: 'read' | 'write'
|
||||||
|
): string[] =>
|
||||||
|
Array.from(
|
||||||
|
new Set(
|
||||||
|
currentGrants()
|
||||||
|
.filter(
|
||||||
|
(grant) => grant.principal_type === principalType && grant.permission === permission
|
||||||
|
)
|
||||||
|
.map((grant) => grant.principal_id)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasPrincipalGrant = (
|
||||||
|
principalType: 'user' | 'group',
|
||||||
|
principalId: string,
|
||||||
|
permission: 'read' | 'write'
|
||||||
|
): boolean =>
|
||||||
|
currentGrants().some(
|
||||||
|
(grant) =>
|
||||||
|
grant.principal_type === principalType &&
|
||||||
|
grant.principal_id === principalId &&
|
||||||
|
grant.permission === permission
|
||||||
|
);
|
||||||
|
|
||||||
|
const commitAccessGrants = (nextGrants: AccessGrant[]) => {
|
||||||
|
accessGrants = dedupeAccessGrants(nextGrants);
|
||||||
|
onChange(accessGrants);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setPublic = (isPublic: boolean) => {
|
||||||
|
const filtered = currentGrants().filter(
|
||||||
|
(grant) =>
|
||||||
|
!(
|
||||||
|
grant.principal_type === 'user' &&
|
||||||
|
grant.principal_id === '*' &&
|
||||||
|
grant.permission === 'read'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (isPublic) {
|
||||||
|
filtered.push({
|
||||||
|
principal_type: 'user',
|
||||||
|
principal_id: '*',
|
||||||
|
permission: 'read'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
commitAccessGrants(filtered);
|
||||||
|
};
|
||||||
|
|
||||||
|
const upsertPrincipalGrant = (
|
||||||
|
principalType: 'user' | 'group',
|
||||||
|
principalId: string,
|
||||||
|
permission: 'read' | 'write',
|
||||||
|
grants: AccessGrant[]
|
||||||
|
): AccessGrant[] => {
|
||||||
|
if (
|
||||||
|
grants.some(
|
||||||
|
(grant) =>
|
||||||
|
grant.principal_type === principalType &&
|
||||||
|
grant.principal_id === principalId &&
|
||||||
|
grant.permission === permission
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return grants;
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
...grants,
|
||||||
|
{
|
||||||
|
principal_type: principalType,
|
||||||
|
principal_id: principalId,
|
||||||
|
permission
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const removePrincipalGrant = (
|
||||||
|
principalType: 'user' | 'group',
|
||||||
|
principalId: string,
|
||||||
|
permission: 'read' | 'write',
|
||||||
|
grants: AccessGrant[]
|
||||||
|
): AccessGrant[] =>
|
||||||
|
grants.filter(
|
||||||
|
(grant) =>
|
||||||
|
!(
|
||||||
|
grant.principal_type === principalType &&
|
||||||
|
grant.principal_id === principalId &&
|
||||||
|
grant.permission === permission
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const removePrincipal = (principalType: 'user' | 'group', principalId: string) => {
|
||||||
|
let next = [...currentGrants()];
|
||||||
|
next = removePrincipalGrant(principalType, principalId, 'read', next);
|
||||||
|
next = removePrincipalGrant(principalType, principalId, 'write', next);
|
||||||
|
commitAccessGrants(next);
|
||||||
|
};
|
||||||
|
|
||||||
|
const togglePrincipalWrite = (principalType: 'user' | 'group', principalId: string) => {
|
||||||
|
let next = [...currentGrants()];
|
||||||
|
const hasWrite = hasPrincipalGrant(principalType, principalId, 'write');
|
||||||
|
if (hasWrite) {
|
||||||
|
next = removePrincipalGrant(principalType, principalId, 'write', next);
|
||||||
|
} else {
|
||||||
|
next = upsertPrincipalGrant(principalType, principalId, 'read', next);
|
||||||
|
next = upsertPrincipalGrant(principalType, principalId, 'write', next);
|
||||||
|
}
|
||||||
|
commitAccessGrants(next);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ensureUsersByIds = async (userIds: string[]) => {
|
||||||
|
const pendingIds = userIds.filter((id) => !userById[id] && !resolvingUserIds.has(id));
|
||||||
|
if (!pendingIds.length) return;
|
||||||
|
|
||||||
|
for (const id of pendingIds) {
|
||||||
|
resolvingUserIds.add(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetched = await Promise.all(
|
||||||
|
pendingIds.map(async (id) => {
|
||||||
|
const user = await getUserInfoById(localStorage.token, id).catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
return { id, user };
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const nextUserById = { ...userById };
|
||||||
|
for (const item of fetched) {
|
||||||
|
if (item.user?.id) {
|
||||||
|
nextUserById[item.id] = item.user;
|
||||||
|
}
|
||||||
|
resolvingUserIds.delete(item.id);
|
||||||
|
}
|
||||||
|
userById = nextUserById;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddAccess = ({ userIds, groupIds }: { userIds: string[]; groupIds: string[] }) => {
|
||||||
|
let next = [...currentGrants()];
|
||||||
|
|
||||||
|
for (const groupId of groupIds) {
|
||||||
|
next = upsertPrincipalGrant('group', groupId, 'read', next);
|
||||||
|
}
|
||||||
|
for (const userId of userIds) {
|
||||||
|
next = upsertPrincipalGrant('user', userId, 'read', next);
|
||||||
|
}
|
||||||
|
commitAccessGrants(next);
|
||||||
|
};
|
||||||
|
|
||||||
|
// NOTE: We must reference `accessGrants` directly in each reactive
|
||||||
|
// expression so Svelte tracks the dependency.
|
||||||
|
const ensureGroupsByIds = async (groupIds: string[]) => {
|
||||||
|
const pendingIds = groupIds.filter(
|
||||||
|
(id) => !groups.find((g) => g.id === id) && !resolvingGroupIds.has(id)
|
||||||
|
);
|
||||||
|
if (!pendingIds.length) return;
|
||||||
|
|
||||||
|
for (const id of pendingIds) {
|
||||||
|
resolvingGroupIds.add(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetched = await Promise.all(
|
||||||
|
pendingIds.map(async (id) => {
|
||||||
|
const group = await getGroupInfoById(localStorage.token, id).catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
return group;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const newGroups = fetched.filter((g) => g);
|
||||||
|
if (newGroups.length > 0) {
|
||||||
|
groups = [...groups, ...newGroups].filter(
|
||||||
|
(g, index, self) => index === self.findIndex((t) => t.id === g.id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const id of pendingIds) {
|
||||||
|
resolvingGroupIds.delete(id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$: if (readGroupIds.length > 0 || writeGroupIds.length > 0) {
|
||||||
|
void ensureGroupsByIds([...readGroupIds, ...writeGroupIds]);
|
||||||
|
}
|
||||||
|
$: readGroupIds = (accessGrants, getPrincipalIdsByPermission('group', 'read'));
|
||||||
|
$: writeGroupIds = (accessGrants, getPrincipalIdsByPermission('group', 'write'));
|
||||||
|
$: readUserIds =
|
||||||
|
(accessGrants, getPrincipalIdsByPermission('user', 'read').filter((id) => id !== '*'));
|
||||||
|
$: writeUserIds =
|
||||||
|
(accessGrants, getPrincipalIdsByPermission('user', 'write').filter((id) => id !== '*'));
|
||||||
|
|
||||||
|
$: selectedUserIds = Array.from(new Set([...readUserIds, ...writeUserIds]));
|
||||||
|
|
||||||
|
$: selectedUsers = selectedUserIds
|
||||||
|
.map((id) => {
|
||||||
|
return userById[id] ?? { id, name: id, email: '' };
|
||||||
|
})
|
||||||
|
.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
|
||||||
|
$: accessGroups = groups
|
||||||
|
.filter((group) => readGroupIds.includes(group.id) || writeGroupIds.includes(group.id))
|
||||||
|
.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
|
||||||
|
$: if (selectedUserIds.length > 0) {
|
||||||
|
void ensureUsersByIds(selectedUserIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if (accessControl !== undefined) {
|
||||||
|
const normalizedGrants = normalizeInputToGrants(accessControl);
|
||||||
|
if (stableStringify(normalizedGrants) !== stableStringify(accessGrants)) {
|
||||||
|
accessGrants = normalizedGrants;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$: {
|
||||||
|
const normalizedGrants = normalizeInputToGrants(accessGrants);
|
||||||
|
if (stableStringify(normalizedGrants) !== stableStringify(accessGrants)) {
|
||||||
|
accessGrants = normalizedGrants;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accessControl !== undefined) {
|
||||||
|
const nextAccessControl = grantsToLegacyAccessControl(normalizedGrants);
|
||||||
|
if (stableStringify(nextAccessControl) !== stableStringify(accessControl)) {
|
||||||
|
accessControl = nextAccessControl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
groups = await getGroups(localStorage.token, true).catch((error) => {
|
console.log('AccessControl mounted', { accessGrants, accessControl });
|
||||||
|
const res = await getGroups(localStorage.token, true).catch((error) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
|
|
||||||
if (accessControl === null) {
|
console.log('getGroups res', res);
|
||||||
initPublicAccess();
|
|
||||||
} else {
|
groups = [...groups, ...res].filter(
|
||||||
accessControl = {
|
(g, index, self) => index === self.findIndex((t) => t.id === g.id)
|
||||||
read: {
|
);
|
||||||
group_ids: accessControl?.read?.group_ids ?? [],
|
});
|
||||||
user_ids: accessControl?.read?.user_ids ?? []
|
|
||||||
},
|
$: console.log('AccessControl state', {
|
||||||
write: {
|
accessGrants,
|
||||||
group_ids: accessControl?.write?.group_ids ?? [],
|
readGroupIds,
|
||||||
user_ids: accessControl?.write?.user_ids ?? []
|
writeGroupIds,
|
||||||
}
|
selectedUserIds,
|
||||||
};
|
groups,
|
||||||
}
|
accessGroups,
|
||||||
|
selectedUsers
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class=" rounded-lg flex flex-col gap-2">
|
<AddAccessModal bind:show={showAddAccessModal} onAdd={handleAddAccess} />
|
||||||
<div class="">
|
|
||||||
<div class=" text-xs font-medium mb-2.5 text-gray-500">{$i18n.t('Visibility')}</div>
|
|
||||||
|
|
||||||
<div class="flex gap-2.5 items-center mb-1">
|
<div class=" rounded-lg flex flex-col gap-1">
|
||||||
|
<div class="py-2">
|
||||||
|
<div class="flex gap-2.5 items-center">
|
||||||
<div>
|
<div>
|
||||||
<div class=" p-2 bg-black/5 dark:bg-white/5 rounded-full">
|
<div class=" p-2 bg-black/5 dark:bg-white/5 rounded-full">
|
||||||
{#if accessControl !== null}
|
{#if !hasPublicReadGrant(accessGrants ?? [])}
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
fill="none"
|
fill="none"
|
||||||
@@ -106,36 +461,28 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<select
|
<Tooltip
|
||||||
id="models"
|
content={!(share && sharePublic) && !hasPublicReadGrant(accessGrants ?? [])
|
||||||
class="dark:bg-gray-900 outline-hidden bg-transparent text-sm font-medium block w-fit pr-10 max-w-full placeholder-gray-400"
|
? $i18n.t('You do not have permission to make this public')
|
||||||
value={accessControl !== null ? 'private' : 'public'}
|
: ''}
|
||||||
on:change={(e) => {
|
|
||||||
if (e.target.value === 'public') {
|
|
||||||
accessControl = null;
|
|
||||||
} else {
|
|
||||||
accessControl = {
|
|
||||||
read: {
|
|
||||||
group_ids: [],
|
|
||||||
user_ids: []
|
|
||||||
},
|
|
||||||
write: {
|
|
||||||
group_ids: [],
|
|
||||||
user_ids: []
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
onChange(accessControl);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<option class=" text-gray-700" value="private" selected>{$i18n.t('Private')}</option>
|
<select
|
||||||
{#if share && sharePublic}
|
id="models"
|
||||||
<option class=" text-gray-700" value="public" selected>{$i18n.t('Public')}</option>
|
class="dark:bg-gray-900 outline-none bg-transparent text-sm font-medium block w-fit pr-10 max-w-full placeholder-gray-400"
|
||||||
{/if}
|
value={!hasPublicReadGrant(accessGrants ?? []) ? 'private' : 'public'}
|
||||||
</select>
|
on:change={(e) => {
|
||||||
|
setPublic((e.target as HTMLSelectElement).value === 'public');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option class=" text-gray-700" value="private">{$i18n.t('Private')}</option>
|
||||||
|
{#if (share && sharePublic) || hasPublicReadGrant(accessGrants ?? [])}
|
||||||
|
<option class=" text-gray-700" value="public">{$i18n.t('Public')}</option>
|
||||||
|
{/if}
|
||||||
|
</select>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
<div class=" text-xs text-gray-400 font-medium">
|
<div class=" text-xs text-gray-400 font-medium">
|
||||||
{#if accessControl !== null}
|
{#if !hasPublicReadGrant(accessGrants ?? [])}
|
||||||
{$i18n.t('Only select users and groups with permission can access')}
|
{$i18n.t('Only select users and groups with permission can access')}
|
||||||
{:else}
|
{:else}
|
||||||
{$i18n.t('Accessible to all users')}
|
{$i18n.t('Accessible to all users')}
|
||||||
@@ -146,116 +493,126 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if share}
|
{#if share}
|
||||||
{#if accessControl !== null}
|
<div class="flex items-center justify-between text-xs font-medium text-gray-500 my-1">
|
||||||
{@const accessGroups = groups.filter((group) =>
|
|
||||||
(accessControl?.read?.group_ids ?? []).includes(group.id)
|
|
||||||
)}
|
|
||||||
<div>
|
<div>
|
||||||
<div class="">
|
{$i18n.t('Access List')}
|
||||||
<div class="flex justify-between mb-2.5">
|
</div>
|
||||||
<div class="text-xs font-medium text-gray-500">
|
<div class="flex gap-1">
|
||||||
{$i18n.t('Groups')}
|
<button
|
||||||
|
class="px-2 py-1 bg-transparent hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition text-xs font-medium flex items-center gap-1"
|
||||||
|
type="button"
|
||||||
|
on:click={() => {
|
||||||
|
showAddAccessModal = true;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Plus className="size-3" />
|
||||||
|
{$i18n.t('Add Access')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- List -->
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<!-- Groups -->
|
||||||
|
{#each accessGroups as group}
|
||||||
|
<div class="flex items-center gap-3 justify-between text-sm w-full transition pb-1">
|
||||||
|
<div class="flex items-center gap-2 w-full flex-1">
|
||||||
|
<!-- Placeholder for group icon vs user icon -->
|
||||||
|
<div
|
||||||
|
class="size-5 rounded-full bg-gray-100 dark:bg-gray-850 flex items-center justify-center text-xs"
|
||||||
|
>
|
||||||
|
{group.name.charAt(0).toUpperCase()}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="truncate text-sm flex items-center gap-2">
|
||||||
|
{group.name}
|
||||||
|
<span class="text-xs text-gray-400 font-normal"
|
||||||
|
>{group?.member_count} {$i18n.t('members')}</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if accessGroups.length > 0}
|
<div class="w-full flex justify-end items-center gap-2">
|
||||||
<div class="flex flex-col gap-1.5 mb-2 px-0.5 mx-0.5">
|
<button
|
||||||
{#each accessGroups as group}
|
type="button"
|
||||||
<div class="flex items-center gap-3 justify-between text-sm w-full transition">
|
on:click={() => {
|
||||||
<div class="flex items-center gap-1.5 w-full">
|
if (accessRoles.includes('write')) {
|
||||||
<div>
|
togglePrincipalWrite('group', group.id);
|
||||||
{group.name} <span class="text-xs text-gray-500">{group?.member_count}</span>
|
}
|
||||||
</div>
|
}}
|
||||||
</div>
|
>
|
||||||
|
{#if writeGroupIds.includes(group.id)}
|
||||||
|
<Badge type={'success'} content={$i18n.t('Write')} />
|
||||||
|
{:else}
|
||||||
|
<Badge type={'info'} content={$i18n.t('Read')} />
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
|
||||||
<div class="w-full flex justify-end items-center gap-0.5">
|
<button
|
||||||
<button
|
class=" rounded-full p-1 hover:bg-gray-100 dark:hover:bg-gray-850 transition"
|
||||||
class=""
|
type="button"
|
||||||
type="button"
|
on:click={() => {
|
||||||
on:click={() => {
|
removePrincipal('group', group.id);
|
||||||
if (accessRoles.includes('write')) {
|
}}
|
||||||
if ((accessControl?.write?.group_ids ?? []).includes(group.id)) {
|
>
|
||||||
accessControl.write.group_ids = (
|
<XMark className="size-4" />
|
||||||
accessControl?.write?.group_ids ?? []
|
</button>
|
||||||
).filter((group_id) => group_id !== group.id);
|
|
||||||
} else {
|
|
||||||
accessControl.write.group_ids = [
|
|
||||||
...(accessControl?.write?.group_ids ?? []),
|
|
||||||
group.id
|
|
||||||
];
|
|
||||||
}
|
|
||||||
onChange(accessControl);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{#if (accessControl?.write?.group_ids ?? []).includes(group.id)}
|
|
||||||
<Badge type={'success'} content={$i18n.t('Write')} />
|
|
||||||
{:else}
|
|
||||||
<Badge type={'info'} content={$i18n.t('Read')} />
|
|
||||||
{/if}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class=" rounded-full p-1 hover:bg-gray-100 dark:hover:bg-gray-850 transition"
|
|
||||||
type="button"
|
|
||||||
on:click={() => {
|
|
||||||
accessControl.read.group_ids = (
|
|
||||||
accessControl?.read?.group_ids ?? []
|
|
||||||
).filter((id) => id !== group.id);
|
|
||||||
accessControl.write.group_ids = (
|
|
||||||
accessControl?.write?.group_ids ?? []
|
|
||||||
).filter((id) => id !== group.id);
|
|
||||||
onChange(accessControl);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<XMark />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<!-- <div class="flex items-center justify-center">
|
|
||||||
<div class="text-gray-500 text-xs text-center py-2 px-10">
|
|
||||||
{$i18n.t('No groups with access, add a group to grant access')}
|
|
||||||
</div>
|
|
||||||
</div> -->
|
|
||||||
|
|
||||||
<div class="mb-1">
|
|
||||||
<div class="flex w-full">
|
|
||||||
<div class="flex flex-1 items-center">
|
|
||||||
<div class="w-full px-0.5">
|
|
||||||
<select
|
|
||||||
class=" outline-hidden bg-transparent text-sm block w-full pr-10 max-w-full
|
|
||||||
{selectedGroupId ? '' : 'text-gray-500'}
|
|
||||||
dark:placeholder-gray-500"
|
|
||||||
bind:value={selectedGroupId}
|
|
||||||
on:change={() => {
|
|
||||||
if (selectedGroupId !== '') {
|
|
||||||
accessControl.read.group_ids = [
|
|
||||||
...(accessControl?.read?.group_ids ?? []),
|
|
||||||
selectedGroupId
|
|
||||||
];
|
|
||||||
|
|
||||||
selectedGroupId = '';
|
|
||||||
onChange(accessControl);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<option class=" text-gray-700" value="" disabled selected
|
|
||||||
>{$i18n.t('Select a group')}</option
|
|
||||||
>
|
|
||||||
{#each groups.filter((group) => !(accessControl?.read?.group_ids ?? []).includes(group.id)) as group}
|
|
||||||
<option class=" text-gray-700" value={group.id}>{group.name}</option>
|
|
||||||
{/each}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{/each}
|
||||||
{/if}
|
|
||||||
|
<!-- Users -->
|
||||||
|
{#each selectedUsers as user}
|
||||||
|
<div
|
||||||
|
class="flex items-center gap-3 justify-between text-sm w-full transition border-b border-gray-50 dark:border-gray-850 pb-2 last:border-0"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-2 w-full flex-1">
|
||||||
|
<img
|
||||||
|
class="rounded-full size-5 object-cover"
|
||||||
|
src={`${WEBUI_API_BASE_URL}/users/${user.id}/profile/image`}
|
||||||
|
alt={user.name ?? user.id}
|
||||||
|
/>
|
||||||
|
<div class="w-full">
|
||||||
|
<Tooltip content={user.email} placement="top-start">
|
||||||
|
<div class="truncate text-sm">{user.name ?? user.id}</div>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w-full flex justify-end items-center gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
on:click={() => {
|
||||||
|
if (accessRoles.includes('write')) {
|
||||||
|
togglePrincipalWrite('user', user.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{#if writeUserIds.includes(user.id)}
|
||||||
|
<Badge type={'success'} content={$i18n.t('Write')} />
|
||||||
|
{:else}
|
||||||
|
<Badge type={'info'} content={$i18n.t('Read')} />
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class=" rounded-full p-1 hover:bg-gray-100 dark:hover:bg-gray-850 transition"
|
||||||
|
type="button"
|
||||||
|
on:click={() => {
|
||||||
|
removePrincipal('user', user.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<XMark className="size-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
{#if !hasPublicReadGrant(accessGrants ?? []) && accessGroups.length === 0 && selectedUsers.length === 0}
|
||||||
|
<div class="text-xs text-gray-500 text-center py-4">
|
||||||
|
{$i18n.t('No access grants. Private to you.')}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { getContext } from 'svelte';
|
import { getContext } from 'svelte';
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
@@ -6,8 +6,16 @@
|
|||||||
import AccessControl from './AccessControl.svelte';
|
import AccessControl from './AccessControl.svelte';
|
||||||
import XMark from '$lib/components/icons/XMark.svelte';
|
import XMark from '$lib/components/icons/XMark.svelte';
|
||||||
|
|
||||||
|
type AccessGrant = {
|
||||||
|
id?: string;
|
||||||
|
principal_type: 'user' | 'group';
|
||||||
|
principal_id: string;
|
||||||
|
permission: 'read' | 'write';
|
||||||
|
};
|
||||||
|
|
||||||
export let show = false;
|
export let show = false;
|
||||||
export let accessControl = {};
|
export let accessGrants: AccessGrant[] = [];
|
||||||
|
export let accessControl: any = undefined;
|
||||||
export let accessRoles = ['read'];
|
export let accessRoles = ['read'];
|
||||||
|
|
||||||
export let share = true;
|
export let share = true;
|
||||||
@@ -33,7 +41,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-full px-5 pb-4 dark:text-white">
|
<div class="w-full px-5 pb-4 dark:text-white">
|
||||||
<AccessControl bind:accessControl {onChange} {accessRoles} {share} {sharePublic} />
|
<AccessControl
|
||||||
|
bind:accessGrants
|
||||||
|
bind:accessControl
|
||||||
|
{onChange}
|
||||||
|
{accessRoles}
|
||||||
|
{share}
|
||||||
|
{sharePublic}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
content: updatedPrompt.content,
|
content: updatedPrompt.content,
|
||||||
version_id: updatedPrompt.version_id,
|
version_id: updatedPrompt.version_id,
|
||||||
tags: updatedPrompt.tags,
|
tags: updatedPrompt.tags,
|
||||||
access_control: updatedPrompt?.access_control === undefined ? {} : updatedPrompt?.access_control
|
access_grants: updatedPrompt?.access_grants === undefined ? [] : updatedPrompt?.access_grants
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
content: _prompt.content,
|
content: _prompt.content,
|
||||||
version_id: _prompt.version_id,
|
version_id: _prompt.version_id,
|
||||||
tags: _prompt.tags,
|
tags: _prompt.tags,
|
||||||
access_control: _prompt?.access_control === undefined ? {} : _prompt?.access_control
|
access_grants: _prompt?.access_grants === undefined ? [] : _prompt?.access_grants
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
goto('/workspace/prompts');
|
goto('/workspace/prompts');
|
||||||
|
|||||||
@@ -10,10 +10,11 @@
|
|||||||
import PromptEditor from '$lib/components/workspace/Prompts/PromptEditor.svelte';
|
import PromptEditor from '$lib/components/workspace/Prompts/PromptEditor.svelte';
|
||||||
|
|
||||||
let prompt: {
|
let prompt: {
|
||||||
title: string;
|
name: string;
|
||||||
command: string;
|
command: string;
|
||||||
content: string;
|
content: string;
|
||||||
access_control: any | null;
|
tags: string[];
|
||||||
|
access_grants: any[];
|
||||||
} | null = null;
|
} | null = null;
|
||||||
|
|
||||||
let clone = false;
|
let clone = false;
|
||||||
@@ -50,7 +51,7 @@
|
|||||||
command: _prompt.command,
|
command: _prompt.command,
|
||||||
content: _prompt.content,
|
content: _prompt.content,
|
||||||
tags: _prompt.tags || [],
|
tags: _prompt.tags || [],
|
||||||
access_control: _prompt.access_control !== undefined ? _prompt.access_control : {}
|
access_grants: _prompt.access_grants !== undefined ? _prompt.access_grants : []
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -70,7 +71,7 @@
|
|||||||
command: _prompt.command,
|
command: _prompt.command,
|
||||||
content: _prompt.content,
|
content: _prompt.content,
|
||||||
tags: _prompt.tags || [],
|
tags: _prompt.tags || [],
|
||||||
access_control: _prompt.access_control !== undefined ? _prompt.access_control : {}
|
access_grants: _prompt.access_grants !== undefined ? _prompt.access_grants : []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
name: data.name,
|
name: data.name,
|
||||||
meta: data.meta,
|
meta: data.meta,
|
||||||
content: data.content,
|
content: data.content,
|
||||||
access_control: data.access_control
|
access_grants: data.access_grants
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
toast.error(`${error}`);
|
toast.error(`${error}`);
|
||||||
return null;
|
return null;
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
name={tool?.name ?? ''}
|
name={tool?.name ?? ''}
|
||||||
meta={tool?.meta ?? { description: '' }}
|
meta={tool?.meta ?? { description: '' }}
|
||||||
content={tool?.content ?? ''}
|
content={tool?.content ?? ''}
|
||||||
accessControl={tool?.access_control !== undefined ? tool.access_control : {}}
|
accessGrants={tool?.access_grants !== undefined ? tool.access_grants : []}
|
||||||
{clone}
|
{clone}
|
||||||
onSave={(value) => {
|
onSave={(value) => {
|
||||||
saveHandler(value);
|
saveHandler(value);
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
name: data.name,
|
name: data.name,
|
||||||
meta: data.meta,
|
meta: data.meta,
|
||||||
content: data.content,
|
content: data.content,
|
||||||
access_control: data.access_control
|
access_grants: data.access_grants
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
toast.error(`${error}`);
|
toast.error(`${error}`);
|
||||||
return null;
|
return null;
|
||||||
@@ -83,7 +83,7 @@
|
|||||||
name={tool.name}
|
name={tool.name}
|
||||||
meta={tool.meta}
|
meta={tool.meta}
|
||||||
content={tool.content}
|
content={tool.content}
|
||||||
accessControl={tool.access_control}
|
accessGrants={tool.access_grants ?? []}
|
||||||
onSave={(value) => {
|
onSave={(value) => {
|
||||||
saveHandler(value);
|
saveHandler(value);
|
||||||
}}
|
}}
|
||||||
|
|||||||
Reference in New Issue
Block a user