mirror of
https://github.com/open-webui/open-webui.git
synced 2026-08-28 05:27:35 +00:00
refactor: remove the orphaned admin settings components
Two components under the admin settings area are not imported by any route or component. The admin settings shell went dead when the admin settings route became a redirect into the settings modal, which imports every admin tab directly and carries its own tab list and search. The model selector beside it lost its last importer in a separate models refactor. Every child component the shell used is still imported by the settings modal, so nothing goes with them. This removes around 590 lines that still turn up in every search across the admin area.
This commit is contained in:
parent
8a42aa53e8
commit
e7671eebb1
2 changed files with 0 additions and 589 deletions
|
|
@ -1,497 +0,0 @@
|
|||
<script>
|
||||
import { getContext, tick, onMount } from 'svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
import { config } from '$lib/stores';
|
||||
import { getBackendConfig } from '$lib/apis';
|
||||
import Database from './Settings/Database.svelte';
|
||||
|
||||
import Authentication from './Settings/Authentication.svelte';
|
||||
import General from './Settings/General.svelte';
|
||||
import Pipelines from './Settings/Pipelines.svelte';
|
||||
import Audio from './Settings/Audio.svelte';
|
||||
import Images from './Settings/Images.svelte';
|
||||
import Interface from './Settings/Interface.svelte';
|
||||
import Models from './Settings/Models.svelte';
|
||||
import Connections from './Settings/Connections.svelte';
|
||||
import Documents from './Settings/Documents.svelte';
|
||||
import WebSearch from './Settings/WebSearch.svelte';
|
||||
|
||||
import Evaluations from './Settings/Evaluations.svelte';
|
||||
import Analytics from './Analytics.svelte';
|
||||
import CodeExecution from './Settings/CodeExecution.svelte';
|
||||
import Integrations from './Settings/Integrations.svelte';
|
||||
import Subagents from './Settings/Subagents.svelte';
|
||||
|
||||
import Search from '../icons/Search.svelte';
|
||||
import AdminTabIcon from './Settings/AdminTabIcon.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
let selectedTab = 'general';
|
||||
|
||||
// Get current tab from URL pathname, default to 'general'
|
||||
$: {
|
||||
const pathParts = $page.url.pathname.split('/');
|
||||
const tabFromPath = pathParts[pathParts.length - 1];
|
||||
selectedTab = [
|
||||
'general',
|
||||
'authentication',
|
||||
'connections',
|
||||
'models',
|
||||
'subagents',
|
||||
'evaluations',
|
||||
'analytics',
|
||||
'integrations',
|
||||
'documents',
|
||||
'web',
|
||||
'code-execution',
|
||||
'interface',
|
||||
'audio',
|
||||
'images',
|
||||
'pipelines',
|
||||
'db'
|
||||
].includes(tabFromPath)
|
||||
? tabFromPath
|
||||
: 'general';
|
||||
}
|
||||
|
||||
$: if (selectedTab) {
|
||||
// scroll to selectedTab
|
||||
scrollToTab(selectedTab);
|
||||
}
|
||||
|
||||
const scrollToTab = (tabId) => {
|
||||
const tabElement = document.getElementById(tabId);
|
||||
if (tabElement) {
|
||||
tabElement.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'start' });
|
||||
}
|
||||
};
|
||||
|
||||
let search = '';
|
||||
let searchDebounceTimeout;
|
||||
let filteredSettings = [];
|
||||
|
||||
const allSettings = [
|
||||
{
|
||||
id: 'general',
|
||||
title: 'General',
|
||||
route: '/admin/settings/general',
|
||||
keywords: [
|
||||
'general',
|
||||
'admin',
|
||||
'settings',
|
||||
'version',
|
||||
'update',
|
||||
'language',
|
||||
'theme',
|
||||
'data',
|
||||
'users',
|
||||
'roles',
|
||||
'ldap',
|
||||
'authentication',
|
||||
'reverse proxy',
|
||||
'webhook',
|
||||
'community',
|
||||
'channels'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'authentication',
|
||||
title: 'Authentication',
|
||||
route: '/admin/settings/authentication',
|
||||
keywords: [
|
||||
'authentication',
|
||||
'auth',
|
||||
'login',
|
||||
'signup',
|
||||
'ldap',
|
||||
'oauth',
|
||||
'oidc',
|
||||
'sso',
|
||||
'roles',
|
||||
'groups',
|
||||
'identity'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'connections',
|
||||
title: 'Connections',
|
||||
route: '/admin/settings/connections',
|
||||
keywords: [
|
||||
'connections',
|
||||
'ollama',
|
||||
'openai',
|
||||
'api',
|
||||
'base url',
|
||||
'direct connections',
|
||||
'proxy',
|
||||
'key'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'models',
|
||||
title: 'Models',
|
||||
route: '/admin/settings/models',
|
||||
keywords: [
|
||||
'models',
|
||||
'pull',
|
||||
'delete',
|
||||
'create',
|
||||
'edit',
|
||||
'modelfile',
|
||||
'gguf',
|
||||
'import',
|
||||
'export'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'subagents',
|
||||
title: 'Sub-agents',
|
||||
route: '/admin/settings/subagents',
|
||||
keywords: ['sub-agents', 'subagents', 'delegation', 'background', 'agents']
|
||||
},
|
||||
{
|
||||
id: 'evaluations',
|
||||
title: 'Evaluations',
|
||||
route: '/admin/settings/evaluations',
|
||||
keywords: ['evaluations', 'feedback', 'rating', 'arena', 'leaderboard', 'preference']
|
||||
},
|
||||
{
|
||||
id: 'analytics',
|
||||
title: 'Analytics',
|
||||
route: '/admin/settings/analytics',
|
||||
keywords: ['analytics', 'usage', 'stats', 'dashboard', 'models', 'users', 'messages']
|
||||
},
|
||||
{
|
||||
id: 'integrations',
|
||||
title: 'Integrations',
|
||||
route: '/admin/settings/integrations',
|
||||
keywords: [
|
||||
'tools',
|
||||
'integrations',
|
||||
'plugins',
|
||||
'extensions',
|
||||
'functions',
|
||||
'openapi',
|
||||
'server',
|
||||
'knowledge',
|
||||
'vector db',
|
||||
'qdrant',
|
||||
'rag',
|
||||
'retrieval',
|
||||
'sources'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'documents',
|
||||
title: 'Documents',
|
||||
route: '/admin/settings/documents',
|
||||
keywords: [
|
||||
'documents',
|
||||
'files',
|
||||
'rag',
|
||||
'knowledge',
|
||||
'upload',
|
||||
'embedding',
|
||||
'vector db',
|
||||
'chunk',
|
||||
'overlap',
|
||||
'splitter',
|
||||
'pdf',
|
||||
'ocr',
|
||||
'tika',
|
||||
'docling',
|
||||
'unstructured'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'web',
|
||||
title: 'Web Search',
|
||||
route: '/admin/settings/web',
|
||||
keywords: [
|
||||
'web search',
|
||||
'google',
|
||||
'bing',
|
||||
'duckduckgo',
|
||||
'serp',
|
||||
'searxng',
|
||||
'moojeh',
|
||||
'yacy',
|
||||
'serper',
|
||||
'serply',
|
||||
'tavily',
|
||||
'exa',
|
||||
'perplexity',
|
||||
'firecrawl'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'code-execution',
|
||||
title: 'Code Execution',
|
||||
route: '/admin/settings/code-execution',
|
||||
keywords: ['code execution', 'python', 'sandbox', 'compiler', 'jupyter', 'interpreter']
|
||||
},
|
||||
{
|
||||
id: 'interface',
|
||||
title: 'Interface',
|
||||
route: '/admin/settings/interface',
|
||||
keywords: [
|
||||
'interface',
|
||||
'ui',
|
||||
'appearance',
|
||||
'banners',
|
||||
'tasks',
|
||||
'prompt suggestions',
|
||||
'title generation',
|
||||
'tags'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'audio',
|
||||
title: 'Audio',
|
||||
route: '/admin/settings/audio',
|
||||
keywords: [
|
||||
'audio',
|
||||
'voice',
|
||||
'speech',
|
||||
'tts',
|
||||
'stt',
|
||||
'whisper',
|
||||
'deepgram',
|
||||
'azure',
|
||||
'openai',
|
||||
'elevenlabs'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'images',
|
||||
title: 'Images',
|
||||
route: '/admin/settings/images',
|
||||
keywords: [
|
||||
'images',
|
||||
'generation',
|
||||
'dalle',
|
||||
'stable diffusion',
|
||||
'comfyui',
|
||||
'automatic1111',
|
||||
'gemini'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'pipelines',
|
||||
title: 'Pipelines',
|
||||
route: '/admin/settings/pipelines',
|
||||
keywords: ['pipelines', 'workflows', 'filters', 'valves', 'middleware']
|
||||
},
|
||||
{
|
||||
id: 'db',
|
||||
title: 'Database',
|
||||
route: '/admin/settings/db',
|
||||
keywords: ['database', 'export', 'import', 'backup', 'chats', 'users']
|
||||
}
|
||||
];
|
||||
|
||||
const setFilteredSettings = () => {
|
||||
filteredSettings = allSettings.filter((tab) => {
|
||||
const searchTerm = search.toLowerCase().trim();
|
||||
if (tab.id === 'analytics' && !($config?.features.enable_admin_analytics ?? true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
search === '' ||
|
||||
tab.title.toLowerCase().includes(searchTerm) ||
|
||||
tab.keywords.some((keyword) => keyword.includes(searchTerm))
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const searchDebounceHandler = () => {
|
||||
if (searchDebounceTimeout) {
|
||||
clearTimeout(searchDebounceTimeout);
|
||||
}
|
||||
|
||||
searchDebounceTimeout = setTimeout(() => {
|
||||
setFilteredSettings();
|
||||
}, 100);
|
||||
};
|
||||
|
||||
const tabButtonClass = (active) =>
|
||||
`flex items-center gap-1.5 h-7 px-2 lg:w-full shrink-0 rounded-lg text-xs text-left transition-colors duration-75 select-none ${
|
||||
active
|
||||
? 'font-medium text-gray-900 dark:text-white bg-gray-100 dark:bg-white/6'
|
||||
: 'text-gray-500 hover:text-gray-700 dark:hover:text-gray-300'
|
||||
}`;
|
||||
|
||||
onMount(() => {
|
||||
const containerElement = document.getElementById('admin-settings-tabs-container');
|
||||
|
||||
if (containerElement) {
|
||||
containerElement.addEventListener('wheel', function (event) {
|
||||
if (event.deltaY !== 0) {
|
||||
// Adjust horizontal scroll position based on vertical scroll
|
||||
containerElement.scrollLeft += event.deltaY;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setFilteredSettings();
|
||||
// Scroll to the selected tab on mount
|
||||
scrollToTab(selectedTab);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col lg:flex-row w-full h-full min-h-0 pb-2">
|
||||
<nav
|
||||
id="admin-settings-tabs-container"
|
||||
class="shrink-0 min-w-0 lg:min-h-0 flex lg:block border-b lg:border-b-0 lg:border-r border-gray-100/30 dark:border-white/[0.02] lg:w-[15rem]"
|
||||
>
|
||||
<div
|
||||
class="tabs flex min-w-0 flex-1 overflow-x-auto lg:overflow-x-hidden lg:overflow-y-auto lg:flex-col p-1 lg:pt-4 gap-px"
|
||||
>
|
||||
<span class="hidden lg:block text-[0.625rem] text-gray-400 dark:text-gray-600 px-2 mb-1">
|
||||
{$i18n.t('Admin')}
|
||||
</span>
|
||||
|
||||
<div
|
||||
class="hidden lg:flex items-center gap-1.5 h-7 px-2 lg:w-full shrink-0 rounded-lg text-xs bg-gray-50/70 dark:bg-white/[0.03] mb-2"
|
||||
id="settings-search"
|
||||
>
|
||||
<div class="self-center rounded-l-xl bg-transparent">
|
||||
<Search className="size-3.5" strokeWidth="1.5" />
|
||||
</div>
|
||||
<label class="sr-only" for="search-input-admin-settings">{$i18n.t('Search')}</label>
|
||||
<input
|
||||
class="w-full py-1 text-xs bg-transparent dark:text-gray-300 outline-hidden"
|
||||
bind:value={search}
|
||||
id="search-input-admin-settings"
|
||||
on:input={searchDebounceHandler}
|
||||
placeholder={$i18n.t('Search')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- {$i18n.t('General')} -->
|
||||
<!-- {$i18n.t('Authentication')} -->
|
||||
<!-- {$i18n.t('Connections')} -->
|
||||
<!-- {$i18n.t('Models')} -->
|
||||
<!-- {$i18n.t('Sub-agents')} -->
|
||||
<!-- {$i18n.t('Evaluations')} -->
|
||||
<!-- {$i18n.t('Analytics')} -->
|
||||
<!-- {$i18n.t('Integrations')} -->
|
||||
<!-- {$i18n.t('Documents')} -->
|
||||
<!-- {$i18n.t('Web Search')} -->
|
||||
<!-- {$i18n.t('Code Execution')} -->
|
||||
<!-- {$i18n.t('Interface')} -->
|
||||
<!-- {$i18n.t('Audio')} -->
|
||||
<!-- {$i18n.t('Images')} -->
|
||||
<!-- {$i18n.t('Pipelines')} -->
|
||||
<!-- {$i18n.t('Database')} -->
|
||||
{#each filteredSettings as tab (tab.id)}
|
||||
<a
|
||||
id={tab.id}
|
||||
href={tab.route}
|
||||
draggable="false"
|
||||
class={tabButtonClass(selectedTab === tab.id)}
|
||||
>
|
||||
<AdminTabIcon id={tab.id} className="size-3.5" strokeWidth="2" />
|
||||
<div class="self-center truncate">{$i18n.t(tab.title)}</div>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="flex-1 min-h-0 p-4 lg:px-5 flex flex-col">
|
||||
<div class="w-full h-full min-h-0 flex flex-col">
|
||||
<div class="flex-1 min-h-0 overflow-hidden">
|
||||
{#if selectedTab === 'general'}
|
||||
<General
|
||||
saveHandler={async () => {
|
||||
toast.success($i18n.t('Settings saved successfully!'));
|
||||
|
||||
await tick();
|
||||
await config.set(await getBackendConfig());
|
||||
}}
|
||||
/>
|
||||
{:else if selectedTab === 'authentication'}
|
||||
<Authentication />
|
||||
{:else if selectedTab === 'connections'}
|
||||
<Connections
|
||||
on:save={() => {
|
||||
toast.success($i18n.t('Settings saved successfully!'));
|
||||
}}
|
||||
/>
|
||||
{:else if selectedTab === 'models'}
|
||||
<Models />
|
||||
{:else if selectedTab === 'subagents'}
|
||||
<Subagents />
|
||||
{:else if selectedTab === 'evaluations'}
|
||||
<Evaluations />
|
||||
{:else if selectedTab === 'analytics'}
|
||||
<Analytics />
|
||||
{:else if selectedTab === 'integrations'}
|
||||
<Integrations />
|
||||
{:else if selectedTab === 'documents'}
|
||||
<Documents
|
||||
on:save={async () => {
|
||||
toast.success($i18n.t('Settings saved successfully!'));
|
||||
|
||||
await tick();
|
||||
await config.set(await getBackendConfig());
|
||||
}}
|
||||
/>
|
||||
{:else if selectedTab === 'web'}
|
||||
<WebSearch
|
||||
saveHandler={async () => {
|
||||
toast.success($i18n.t('Settings saved successfully!'));
|
||||
|
||||
await tick();
|
||||
await config.set(await getBackendConfig());
|
||||
}}
|
||||
/>
|
||||
{:else if selectedTab === 'code-execution'}
|
||||
<CodeExecution
|
||||
saveHandler={async () => {
|
||||
toast.success($i18n.t('Settings saved successfully!'));
|
||||
|
||||
await tick();
|
||||
await config.set(await getBackendConfig());
|
||||
}}
|
||||
/>
|
||||
{:else if selectedTab === 'interface'}
|
||||
<Interface
|
||||
on:save={() => {
|
||||
toast.success($i18n.t('Settings saved successfully!'));
|
||||
}}
|
||||
/>
|
||||
{:else if selectedTab === 'audio'}
|
||||
<Audio
|
||||
saveHandler={() => {
|
||||
toast.success($i18n.t('Settings saved successfully!'));
|
||||
}}
|
||||
/>
|
||||
{:else if selectedTab === 'images'}
|
||||
<Images
|
||||
on:save={() => {
|
||||
toast.success($i18n.t('Settings saved successfully!'));
|
||||
}}
|
||||
/>
|
||||
{:else if selectedTab === 'db'}
|
||||
<Database
|
||||
saveHandler={() => {
|
||||
toast.success($i18n.t('Settings saved successfully!'));
|
||||
}}
|
||||
/>
|
||||
{:else if selectedTab === 'pipelines'}
|
||||
<Pipelines
|
||||
saveHandler={() => {
|
||||
toast.success($i18n.t('Settings saved successfully!'));
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { getContext } from 'svelte';
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
import Minus from '$lib/components/icons/Minus.svelte';
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
import SettingsSelect from '$lib/components/common/SettingsSelect.svelte';
|
||||
|
||||
export let title = '';
|
||||
export let tooltip = '';
|
||||
export let models = [];
|
||||
export let modelIds = [];
|
||||
|
||||
let selectedModelId = '';
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<div class="flex flex-col w-full">
|
||||
<div class="mb-1 flex justify-between">
|
||||
<div class="text-xs text-gray-500 flex items-center gap-1">
|
||||
{title}
|
||||
{#if tooltip}
|
||||
<Tooltip content={tooltip} className="cursor-help">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-3"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z"
|
||||
/>
|
||||
</svg>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center -mr-1">
|
||||
<SettingsSelect
|
||||
className="w-full"
|
||||
selectClassName="text-sm"
|
||||
bind:value={selectedModelId}
|
||||
on:change={() => {
|
||||
if (selectedModelId && !modelIds.includes(selectedModelId)) {
|
||||
modelIds = [...modelIds, selectedModelId];
|
||||
}
|
||||
selectedModelId = '';
|
||||
}}
|
||||
>
|
||||
<option value="">{$i18n.t('Select a model')}</option>
|
||||
{#each models as model}
|
||||
{#if !modelIds.includes(model.id)}
|
||||
<option value={model.id} class="bg-gray-50 dark:bg-gray-700">{model.name}</option>
|
||||
{/if}
|
||||
{/each}
|
||||
</SettingsSelect>
|
||||
</div>
|
||||
|
||||
<!-- <hr class=" border-gray-100 dark:border-gray-700/10 my-2.5 w-full" /> -->
|
||||
|
||||
{#if modelIds.length > 0}
|
||||
<div class="flex flex-col">
|
||||
{#each modelIds as modelId, modelIdx}
|
||||
<div class=" flex gap-2 w-full justify-between items-center">
|
||||
<div class=" text-sm flex-1 py-1 rounded-lg">
|
||||
{models.find((model) => model.id === modelId)?.name}
|
||||
</div>
|
||||
<div class="shrink-0">
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => {
|
||||
modelIds = modelIds.filter((_, idx) => idx !== modelIdx);
|
||||
}}
|
||||
>
|
||||
<Minus strokeWidth="2" className="size-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-gray-500 text-xs text-center py-2">
|
||||
{$i18n.t('No models selected')}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
Loading…
Add table
Reference in a new issue