mirror of
https://github.com/open-webui/open-webui.git
synced 2026-04-25 17:15:16 +02:00
refac
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# Open WebUI Contributor License Agreement
|
||||
# Contributor License Agreement
|
||||
|
||||
By submitting my contributions to Open WebUI, I grant Open WebUI full freedom to use my work in any way they choose, under any terms they like, both now and in the future. This approach helps ensure the project remains unified, flexible, and easy to maintain, while empowering Open WebUI to respond quickly to the needs of its users and the wider community.
|
||||
By submitting my contributions to this repository in any form, I grant Open WebUI Inc. a perpetual, worldwide, irrevocable, royalty-free license, under copyright and patent, to use, modify, distribute, sublicense, and commercialize my work under any terms they choose, both now and in the future.
|
||||
|
||||
Taking part in this process means my work can be seamlessly integrated and combined with others, ensuring longevity and adaptability for everyone who benefits from the Open WebUI project. This collaborative approach strengthens the project’s future and helps guarantee that improvements can always be shared and distributed in the most effective way possible.
|
||||
I represent that my contributions are my original work (or that I have sufficient rights to grant this license) and that I have the authority to enter into this agreement.
|
||||
|
||||
**_To the fullest extent permitted by law, my contributions are provided on an “as is” basis, with no warranties or guarantees of any kind, and I disclaim any liability for any issues or damages arising from their use or incorporation into the project, regardless of the type of legal claim._**
|
||||
@@ -19,7 +19,8 @@
|
||||
import Tags from './common/Tags.svelte';
|
||||
import { getToolServerData } from '$lib/apis';
|
||||
import { verifyToolServerConnection, registerOAuthClient } from '$lib/apis/configs';
|
||||
import AccessControl from './workspace/common/AccessControl.svelte';
|
||||
import AccessControlModal from '$lib/components/workspace/common/AccessControlModal.svelte';
|
||||
import LockClosed from '$lib/components/icons/LockClosed.svelte';
|
||||
import Spinner from '$lib/components/common/Spinner.svelte';
|
||||
import XMark from '$lib/components/icons/XMark.svelte';
|
||||
import Textarea from './common/Textarea.svelte';
|
||||
@@ -59,6 +60,7 @@
|
||||
let enable = true;
|
||||
let loading = false;
|
||||
let showAdvanced = false;
|
||||
let showAccessControlModal = false;
|
||||
|
||||
const registerOAuthClientHandler = async () => {
|
||||
if (url === '') {
|
||||
@@ -440,30 +442,94 @@
|
||||
}}
|
||||
>
|
||||
<div class="px-1">
|
||||
{#if !direct}
|
||||
<div class="flex gap-2 mb-1.5">
|
||||
<div class="flex w-full justify-between items-center">
|
||||
<div class=" text-xs text-gray-500">{$i18n.t('Type')}</div>
|
||||
<div class="flex gap-2 mb-1.5">
|
||||
<div class="flex w-full justify-between items-center">
|
||||
<div class=" text-xs text-gray-500">{$i18n.t('Type')}</div>
|
||||
|
||||
<div class="">
|
||||
<button
|
||||
on:click={() => {
|
||||
type = ['', 'openapi'].includes(type) ? 'mcp' : 'openapi';
|
||||
}}
|
||||
type="button"
|
||||
class=" text-xs text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
{#if ['', 'openapi'].includes(type)}
|
||||
{$i18n.t('OpenAPI')}
|
||||
{:else if type === 'mcp'}
|
||||
{$i18n.t('MCP')}
|
||||
<span class="text-gray-500">{$i18n.t('Streamable HTTP')}</span>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
<div class="">
|
||||
<button
|
||||
on:click={() => {
|
||||
type = ['', 'openapi'].includes(type) ? 'mcp' : 'openapi';
|
||||
}}
|
||||
type="button"
|
||||
class=" text-xs text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
{#if ['', 'openapi'].includes(type)}
|
||||
{$i18n.t('OpenAPI')}
|
||||
{:else if type === 'mcp'}
|
||||
{$i18n.t('MCP')}
|
||||
<span class="text-gray-500">{$i18n.t('Streamable HTTP')}</span>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<div class="flex flex-col flex-1">
|
||||
<div class="flex justify-between mb-0.5">
|
||||
<label
|
||||
for="enter-name"
|
||||
class={`text-xs ${($settings?.highContrastMode ?? false) ? 'text-gray-800 dark:text-gray-100' : 'text-gray-500'}`}
|
||||
>{$i18n.t('Name')}</label
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-1 items-center">
|
||||
<input
|
||||
id="enter-name"
|
||||
class={`w-full flex-1 text-sm bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
|
||||
type="text"
|
||||
bind:value={name}
|
||||
placeholder={$i18n.t('Enter name')}
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{#if !direct}
|
||||
<div class="flex flex-col flex-1">
|
||||
<div class="flex justify-between mb-0.5">
|
||||
<label
|
||||
for="enter-id"
|
||||
class={`text-xs ${($settings?.highContrastMode ?? false) ? 'text-gray-800 dark:text-gray-100' : 'text-gray-500'}`}
|
||||
>{$i18n.t('ID')}
|
||||
{#if type !== 'mcp'}<span class="opacity-50">({$i18n.t('optional')})</span
|
||||
>{/if}</label
|
||||
>
|
||||
</div>
|
||||
<div class="flex flex-1 items-center">
|
||||
<input
|
||||
id="enter-id"
|
||||
class={`w-full flex-1 text-sm bg-transparent font-mono ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
|
||||
type="text"
|
||||
bind:value={id}
|
||||
placeholder="auto"
|
||||
autocomplete="off"
|
||||
required={type === 'mcp'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col w-full mt-1 mb-1.5">
|
||||
<label
|
||||
for="description"
|
||||
class={`mb-0.5 text-xs ${($settings?.highContrastMode ?? false) ? 'text-gray-800 dark:text-gray-100' : 'text-gray-500'}`}
|
||||
>{$i18n.t('Description')}</label
|
||||
>
|
||||
|
||||
<div class="flex-1">
|
||||
<input
|
||||
id="description"
|
||||
class={`w-full text-sm bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
|
||||
type="text"
|
||||
bind:value={description}
|
||||
placeholder={$i18n.t('Enter description')}
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<div class="flex flex-col w-full">
|
||||
@@ -521,103 +587,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="flex items-center gap-1 text-xs text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition mt-2"
|
||||
on:click={() => (showAdvanced = !showAdvanced)}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="w-3 h-3 transition-transform {showAdvanced ? 'rotate-90' : ''}"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
{$i18n.t('Advanced')}
|
||||
</button>
|
||||
|
||||
{#if showAdvanced}
|
||||
{#if ['', 'openapi'].includes(type)}
|
||||
<div class="flex gap-2 mt-2">
|
||||
<div class="flex flex-col w-full">
|
||||
<div class="flex justify-between items-center mb-0.5">
|
||||
<div class="flex gap-2 items-center">
|
||||
<div
|
||||
for="select-bearer-or-session"
|
||||
class={`text-xs ${($settings?.highContrastMode ?? false) ? 'text-gray-800 dark:text-gray-100' : 'text-gray-500'}`}
|
||||
>
|
||||
{$i18n.t('OpenAPI Spec')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<div class="flex-shrink-0 self-start">
|
||||
<select
|
||||
id="select-bearer-or-session"
|
||||
class={`dark:bg-gray-900 w-full text-sm bg-transparent pr-5 ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
|
||||
bind:value={spec_type}
|
||||
>
|
||||
<option value="url">{$i18n.t('URL')}</option>
|
||||
<option value="json">{$i18n.t('JSON')}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-1 items-center">
|
||||
{#if spec_type === 'url'}
|
||||
<div class="flex-1 flex items-center">
|
||||
<label for="url-or-path" class="sr-only"
|
||||
>{$i18n.t('openapi.json URL or Path')}</label
|
||||
>
|
||||
<input
|
||||
class={`w-full text-sm bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
|
||||
type="text"
|
||||
id="url-or-path"
|
||||
bind:value={path}
|
||||
placeholder={$i18n.t('openapi.json URL or Path')}
|
||||
autocomplete="off"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
{:else if spec_type === 'json'}
|
||||
<div
|
||||
class={`text-xs w-full self-center translate-y-[1px] ${($settings?.highContrastMode ?? false) ? 'text-gray-800 dark:text-gray-100' : 'text-gray-500'}`}
|
||||
>
|
||||
<label for="url-or-path" class="sr-only">{$i18n.t('JSON Spec')}</label>
|
||||
<textarea
|
||||
class={`w-full text-sm bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700 text-black dark:text-white'}`}
|
||||
bind:value={spec}
|
||||
placeholder={$i18n.t('JSON Spec')}
|
||||
autocomplete="off"
|
||||
required
|
||||
rows="5"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if ['', 'url'].includes(spec_type)}
|
||||
<div
|
||||
class={`text-xs mt-1 ${($settings?.highContrastMode ?? false) ? 'text-gray-800 dark:text-gray-100' : 'text-gray-500'}`}
|
||||
>
|
||||
{$i18n.t(`WebUI will make requests to "{{url}}"`, {
|
||||
url: path.includes('://')
|
||||
? path
|
||||
: `${url}${path.startsWith('/') ? '' : '/'}${path}`
|
||||
})}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<div class="flex gap-2 mt-2">
|
||||
<div class="flex flex-col w-full">
|
||||
<div class="flex justify-between items-center">
|
||||
@@ -725,104 +694,152 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if !direct}
|
||||
<div class="flex gap-2 mt-2">
|
||||
<div class="flex flex-col w-full">
|
||||
<label
|
||||
for="headers-input"
|
||||
class={`mb-0.5 text-xs text-gray-500
|
||||
${($settings?.highContrastMode ?? false) ? 'text-gray-800 dark:text-gray-100' : ''}`}
|
||||
>{$i18n.t('Headers')}</label
|
||||
>
|
||||
|
||||
<div class="flex-1">
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
'Enter additional headers in JSON format (e.g. {"X-Custom-Header": "value"}'
|
||||
)}
|
||||
>
|
||||
<Textarea
|
||||
className="w-full text-sm outline-hidden"
|
||||
bind:value={headers}
|
||||
placeholder={$i18n.t('Enter additional headers in JSON format')}
|
||||
required={false}
|
||||
minSize={30}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-100 dark:border-gray-700/10 my-2.5 w-full" />
|
||||
|
||||
<div class="flex gap-2">
|
||||
<div class="flex flex-col w-full">
|
||||
<label
|
||||
for="enter-id"
|
||||
class={`mb-0.5 text-xs ${($settings?.highContrastMode ?? false) ? 'text-gray-800 dark:text-gray-100' : 'text-gray-500'}`}
|
||||
>{$i18n.t('ID')}
|
||||
|
||||
{#if type !== 'mcp'}
|
||||
<span class="text-xs text-gray-200 dark:text-gray-800 ml-0.5"
|
||||
>{$i18n.t('Optional')}</span
|
||||
>
|
||||
{/if}
|
||||
</label>
|
||||
|
||||
<div class="flex-1">
|
||||
<input
|
||||
id="enter-id"
|
||||
class={`w-full text-sm bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
|
||||
type="text"
|
||||
bind:value={id}
|
||||
placeholder={$i18n.t('Enter ID')}
|
||||
autocomplete="off"
|
||||
required={type === 'mcp'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 mt-2">
|
||||
<div class="flex flex-col w-full">
|
||||
<label
|
||||
for="enter-name"
|
||||
class={`mb-0.5 text-xs ${($settings?.highContrastMode ?? false) ? 'text-gray-800 dark:text-gray-100' : 'text-gray-500'}`}
|
||||
>{$i18n.t('Name')}
|
||||
</label>
|
||||
|
||||
<div class="flex-1">
|
||||
<input
|
||||
id="enter-name"
|
||||
class={`w-full text-sm bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
|
||||
type="text"
|
||||
bind:value={name}
|
||||
placeholder={$i18n.t('Enter name')}
|
||||
autocomplete="off"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col w-full mt-2">
|
||||
<label
|
||||
for="description"
|
||||
class={`mb-1 text-xs ${($settings?.highContrastMode ?? false) ? 'text-gray-800 dark:text-gray-100 placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700 text-gray-500'}`}
|
||||
>{$i18n.t('Description')}</label
|
||||
<div class="flex items-center justify-between">
|
||||
<button
|
||||
type="button"
|
||||
class="flex items-center gap-1 text-xs text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition mt-2"
|
||||
on:click={() => (showAdvanced = !showAdvanced)}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="w-3 h-3 transition-transform {showAdvanced ? 'rotate-90' : ''}"
|
||||
>
|
||||
|
||||
<div class="flex-1">
|
||||
<input
|
||||
id="description"
|
||||
class={`w-full text-sm bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
|
||||
type="text"
|
||||
bind:value={description}
|
||||
placeholder={$i18n.t('Enter description')}
|
||||
autocomplete="off"
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
{$i18n.t('Advanced')}
|
||||
</button>
|
||||
|
||||
{#if !direct}
|
||||
<button
|
||||
class="bg-gray-50 hover:bg-gray-100 text-black dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-white transition px-2 py-1 object-cover rounded-full flex gap-1 items-center mt-2"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
showAccessControlModal = true;
|
||||
}}
|
||||
>
|
||||
<LockClosed strokeWidth="2.5" className="size-3.5 shrink-0" />
|
||||
|
||||
<div class="text-xs font-medium shrink-0">
|
||||
{$i18n.t('Access')}
|
||||
</div>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if showAdvanced}
|
||||
{#if ['', 'openapi'].includes(type)}
|
||||
<div class="flex gap-2 mt-2">
|
||||
<div class="flex flex-col w-full">
|
||||
<div class="flex justify-between items-center mb-0.5">
|
||||
<div class="flex gap-2 items-center">
|
||||
<div
|
||||
for="select-bearer-or-session"
|
||||
class={`text-xs ${($settings?.highContrastMode ?? false) ? 'text-gray-800 dark:text-gray-100' : 'text-gray-500'}`}
|
||||
>
|
||||
{$i18n.t('OpenAPI Spec')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<div class="flex-shrink-0 self-start">
|
||||
<select
|
||||
id="select-bearer-or-session"
|
||||
class={`dark:bg-gray-900 w-full text-sm bg-transparent pr-5 ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
|
||||
bind:value={spec_type}
|
||||
>
|
||||
<option value="url">{$i18n.t('URL')}</option>
|
||||
<option value="json">{$i18n.t('JSON')}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-1 items-center">
|
||||
{#if spec_type === 'url'}
|
||||
<div class="flex-1 flex items-center">
|
||||
<label for="url-or-path" class="sr-only"
|
||||
>{$i18n.t('openapi.json URL or Path')}</label
|
||||
>
|
||||
<input
|
||||
class={`w-full text-sm bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
|
||||
type="text"
|
||||
id="url-or-path"
|
||||
bind:value={path}
|
||||
placeholder={$i18n.t('openapi.json URL or Path')}
|
||||
autocomplete="off"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
{:else if spec_type === 'json'}
|
||||
<div
|
||||
class={`text-xs w-full self-center translate-y-[1px] ${($settings?.highContrastMode ?? false) ? 'text-gray-800 dark:text-gray-100' : 'text-gray-500'}`}
|
||||
>
|
||||
<label for="url-or-path" class="sr-only">{$i18n.t('JSON Spec')}</label>
|
||||
<textarea
|
||||
class={`w-full text-sm bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700 text-black dark:text-white'}`}
|
||||
bind:value={spec}
|
||||
placeholder={$i18n.t('JSON Spec')}
|
||||
autocomplete="off"
|
||||
required
|
||||
rows="5"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if ['', 'url'].includes(spec_type)}
|
||||
<div
|
||||
class={`text-xs mt-1 ${($settings?.highContrastMode ?? false) ? 'text-gray-800 dark:text-gray-100' : 'text-gray-500'}`}
|
||||
>
|
||||
{$i18n.t(`WebUI will make requests to "{{url}}"`, {
|
||||
url: path.includes('://')
|
||||
? path
|
||||
: `${url}${path.startsWith('/') ? '' : '/'}${path}`
|
||||
})}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if !direct}
|
||||
<div class="flex gap-2 mt-2">
|
||||
<div class="flex flex-col w-full">
|
||||
<label
|
||||
for="headers-input"
|
||||
class={`mb-0.5 text-xs text-gray-500
|
||||
${($settings?.highContrastMode ?? false) ? 'text-gray-800 dark:text-gray-100' : ''}`}
|
||||
>{$i18n.t('Headers')}</label
|
||||
>
|
||||
|
||||
<div class="flex-1">
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
'Enter additional headers in JSON format (e.g. {"X-Custom-Header": "value"}'
|
||||
)}
|
||||
>
|
||||
<Textarea
|
||||
className="w-full text-sm outline-hidden"
|
||||
bind:value={headers}
|
||||
placeholder={$i18n.t('Enter additional headers in JSON format')}
|
||||
required={false}
|
||||
minSize={30}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if !direct}
|
||||
<hr class=" border-gray-100 dark:border-gray-700/10 my-2.5 w-full" />
|
||||
|
||||
<div class="flex flex-col w-full mt-2">
|
||||
<label
|
||||
@@ -842,12 +859,6 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-100 dark:border-gray-700/10 my-2.5 w-full" />
|
||||
|
||||
<div class="my-2">
|
||||
<AccessControl bind:accessGrants />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -908,3 +919,5 @@
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<AccessControlModal bind:show={showAccessControlModal} bind:accessGrants />
|
||||
|
||||
Reference in New Issue
Block a user