refac
This commit is contained in:
@@ -18,7 +18,7 @@
|
||||
</script>
|
||||
|
||||
{#if loaded}
|
||||
<div class="w-full h-full pb-2 px-3">
|
||||
<div class="w-full h-full pb-2">
|
||||
<Dashboard />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -230,13 +230,10 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Header with title and period selector -->
|
||||
<div
|
||||
class="pt-0.5 pb-1 gap-1 flex flex-row justify-between items-center sticky top-0 z-10 bg-white dark:bg-gray-900"
|
||||
>
|
||||
<div class="text-lg font-normal px-0.5 shrink-0">
|
||||
<div class="flex items-center justify-between mb-2 gap-2">
|
||||
<h2 class="text-sm font-medium text-gray-900 dark:text-white shrink-0">
|
||||
{$i18n.t('Analytics')}
|
||||
</div>
|
||||
</h2>
|
||||
<div class="flex items-center gap-2 flex-wrap justify-end min-w-0">
|
||||
{#if groups.length > 0}
|
||||
<select
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
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';
|
||||
@@ -42,6 +43,7 @@
|
||||
'models',
|
||||
'subagents',
|
||||
'evaluations',
|
||||
'analytics',
|
||||
'integrations',
|
||||
'documents',
|
||||
'web',
|
||||
@@ -157,6 +159,12 @@
|
||||
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',
|
||||
@@ -289,6 +297,10 @@
|
||||
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) ||
|
||||
@@ -367,6 +379,7 @@
|
||||
<!-- {$i18n.t('Models')} -->
|
||||
<!-- {$i18n.t('Sub-agents')} -->
|
||||
<!-- {$i18n.t('Evaluations')} -->
|
||||
<!-- {$i18n.t('Analytics')} -->
|
||||
<!-- {$i18n.t('Integrations')} -->
|
||||
<!-- {$i18n.t('Documents')} -->
|
||||
<!-- {$i18n.t('Web Search')} -->
|
||||
@@ -416,6 +429,8 @@
|
||||
<Subagents />
|
||||
{:else if selectedTab === 'evaluations'}
|
||||
<Evaluations />
|
||||
{:else if selectedTab === 'analytics'}
|
||||
<Analytics />
|
||||
{:else if selectedTab === 'integrations'}
|
||||
<Integrations />
|
||||
{:else if selectedTab === 'documents'}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import CodeBracket from '$lib/components/icons/CodeBracket.svelte';
|
||||
import BarsArrowUp from '$lib/components/icons/BarsArrowUp.svelte';
|
||||
import Computer from '$lib/components/icons/Computer.svelte';
|
||||
import Database from '$lib/components/icons/Database.svelte';
|
||||
import Document from '$lib/components/icons/Document.svelte';
|
||||
@@ -66,6 +67,8 @@
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 20V4" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M19 20v-7" />
|
||||
</svg>
|
||||
{:else if id === 'analytics'}
|
||||
<BarsArrowUp {className} {strokeWidth} />
|
||||
{:else if id === 'integrations'}
|
||||
<WrenchAlt {className} {strokeWidth} />
|
||||
{:else if id === 'documents'}
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
|
||||
import ModelEditor from '$lib/components/workspace/Models/ModelEditor.svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import Badge from '$lib/components/common/Badge.svelte';
|
||||
import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
|
||||
import ModelSettingsModal from './Models/ModelSettingsModal.svelte';
|
||||
import ManageModelsModal from './Models/ManageModelsModal.svelte';
|
||||
@@ -85,14 +84,24 @@
|
||||
|
||||
const isSharedModel = (model) => (model?.access_grants ?? []).length > 0 && !isPublicModel(model);
|
||||
|
||||
const modelAccessBadge = (model) => {
|
||||
const modelAccessLabel = (model) => {
|
||||
if (isPublicModel(model)) {
|
||||
return { type: 'success', content: $i18n.t('Public') };
|
||||
return $i18n.t('Public');
|
||||
}
|
||||
if (isSharedModel(model)) {
|
||||
return { type: 'info', content: $i18n.t('Shared') };
|
||||
return $i18n.t('Shared');
|
||||
}
|
||||
return { type: 'muted', content: $i18n.t('Private') };
|
||||
return $i18n.t('Private');
|
||||
};
|
||||
|
||||
const modelAccessClass = (model) => {
|
||||
if (isPublicModel(model)) {
|
||||
return 'text-green-700/50 dark:text-green-300/50';
|
||||
}
|
||||
if (isSharedModel(model)) {
|
||||
return 'text-blue-700/50 dark:text-blue-300/50';
|
||||
}
|
||||
return 'text-gray-500 dark:text-gray-400';
|
||||
};
|
||||
|
||||
$: if (models) {
|
||||
@@ -670,7 +679,7 @@
|
||||
<img
|
||||
src={`${WEBUI_API_BASE_URL}/models/model/profile/image?id=${model.id}&lang=${$i18n.language}`}
|
||||
alt="modelfile profile"
|
||||
class=" rounded-xl size-8 object-cover"
|
||||
class=" rounded-xl size-7 object-cover"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
on:error={(e) => {
|
||||
@@ -711,10 +720,13 @@
|
||||
: model.id}
|
||||
</span>
|
||||
|
||||
<Badge
|
||||
type={modelAccessBadge(model).type}
|
||||
content={modelAccessBadge(model).content}
|
||||
/>
|
||||
<span
|
||||
class="shrink-0 text-[11px] font-normal leading-4 {modelAccessClass(
|
||||
model
|
||||
)}"
|
||||
>
|
||||
{modelAccessLabel(model)}
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
import Checkbox from '$lib/components/common/Checkbox.svelte';
|
||||
import Badge from '$lib/components/common/Badge.svelte';
|
||||
import Search from '$lib/components/icons/Search.svelte';
|
||||
import Pagination from '$lib/components/common/Pagination.svelte';
|
||||
import ChevronDown from '$lib/components/icons/ChevronDown.svelte';
|
||||
@@ -46,6 +45,16 @@
|
||||
page = 1;
|
||||
};
|
||||
|
||||
const roleClass = (role) => {
|
||||
if (role === 'admin') {
|
||||
return 'text-blue-700/50 dark:text-blue-300/50';
|
||||
}
|
||||
if (role === 'user') {
|
||||
return 'text-green-700/50 dark:text-green-300/50';
|
||||
}
|
||||
return 'text-gray-500 dark:text-gray-400';
|
||||
};
|
||||
|
||||
const getUserList = async () => {
|
||||
try {
|
||||
const res = await getUsers(localStorage.token, query, orderBy, direction, page).catch(
|
||||
@@ -154,12 +163,12 @@
|
||||
<th
|
||||
scope="col"
|
||||
class="px-2.5 py-2 cursor-pointer select-none"
|
||||
on:click={() => setSortKey('role')}
|
||||
on:click={() => setSortKey('name')}
|
||||
>
|
||||
<div class="flex gap-1.5 items-center">
|
||||
{$i18n.t('Role')}
|
||||
{$i18n.t('Name')}
|
||||
|
||||
{#if orderBy === 'role'}
|
||||
{#if orderBy === 'name'}
|
||||
<span class="font-normal"
|
||||
>{#if direction === 'asc'}
|
||||
<ChevronUp className="size-2" />
|
||||
@@ -177,12 +186,12 @@
|
||||
<th
|
||||
scope="col"
|
||||
class="px-2.5 py-2 cursor-pointer select-none"
|
||||
on:click={() => setSortKey('name')}
|
||||
on:click={() => setSortKey('role')}
|
||||
>
|
||||
<div class="flex gap-1.5 items-center">
|
||||
{$i18n.t('Name')}
|
||||
{$i18n.t('Role')}
|
||||
|
||||
{#if orderBy === 'name'}
|
||||
{#if orderBy === 'role'}
|
||||
<span class="font-normal"
|
||||
>{#if direction === 'asc'}
|
||||
<ChevronUp className="size-2" />
|
||||
@@ -236,23 +245,11 @@
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-3 py-1 min-w-[7rem] w-28">
|
||||
<div class=" translate-y-0.5">
|
||||
<Badge
|
||||
type={user.role === 'admin'
|
||||
? 'info'
|
||||
: user.role === 'user'
|
||||
? 'success'
|
||||
: 'muted'}
|
||||
content={$i18n.t(user.role)}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-3 py-1 font-normal text-gray-900 dark:text-white max-w-48">
|
||||
<Tooltip content={user.email} placement="top-start">
|
||||
<div class="flex items-center">
|
||||
<div class="flex items-center gap-2">
|
||||
<img
|
||||
class="rounded-full w-6 h-6 object-cover mr-2.5 flex-shrink-0"
|
||||
class="rounded-full w-6 h-6 object-cover flex-shrink-0"
|
||||
src={`${WEBUI_API_BASE_URL}/users/${user.id}/profile/image`}
|
||||
alt="user"
|
||||
/>
|
||||
@@ -261,6 +258,11 @@
|
||||
</div>
|
||||
</Tooltip>
|
||||
</td>
|
||||
<td class="px-3 py-1 min-w-[5rem] w-20">
|
||||
<span class="text-xs font-normal leading-4 capitalize {roleClass(user.role)}">
|
||||
{$i18n.t(user.role)}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<td class=" px-3 py-1">
|
||||
{dayjs(user.last_active_at * 1000).fromNow()}
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
|
||||
import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
|
||||
|
||||
import Badge from '$lib/components/common/Badge.svelte';
|
||||
import Plus from '$lib/components/icons/Plus.svelte';
|
||||
import ChevronUp from '$lib/components/icons/ChevronUp.svelte';
|
||||
import ChevronDown from '$lib/components/icons/ChevronDown.svelte';
|
||||
@@ -79,6 +78,16 @@
|
||||
}
|
||||
};
|
||||
|
||||
const roleClass = (role) => {
|
||||
if (role === 'admin') {
|
||||
return 'text-blue-700/50 dark:text-blue-300/50';
|
||||
}
|
||||
if (role === 'user') {
|
||||
return 'text-green-700/50 dark:text-green-300/50';
|
||||
}
|
||||
return 'text-gray-500 dark:text-gray-400';
|
||||
};
|
||||
|
||||
const getUserList = async () => {
|
||||
try {
|
||||
const res = await getUsers(localStorage.token, query, orderBy, direction, page).catch(
|
||||
@@ -239,12 +248,12 @@
|
||||
<th
|
||||
scope="col"
|
||||
class="px-2.5 py-2 cursor-pointer select-none"
|
||||
on:click={() => setSortKey('role')}
|
||||
on:click={() => setSortKey('name')}
|
||||
>
|
||||
<div class="flex gap-1.5 items-center">
|
||||
{$i18n.t('Role')}
|
||||
{$i18n.t('Name')}
|
||||
|
||||
{#if orderBy === 'role'}
|
||||
{#if orderBy === 'name'}
|
||||
<span class="font-normal"
|
||||
>{#if direction === 'asc'}
|
||||
<ChevronUp className="size-2" />
|
||||
@@ -262,12 +271,12 @@
|
||||
<th
|
||||
scope="col"
|
||||
class="px-2.5 py-2 cursor-pointer select-none"
|
||||
on:click={() => setSortKey('name')}
|
||||
on:click={() => setSortKey('role')}
|
||||
>
|
||||
<div class="flex gap-1.5 items-center">
|
||||
{$i18n.t('Name')}
|
||||
{$i18n.t('Role')}
|
||||
|
||||
{#if orderBy === 'name'}
|
||||
{#if orderBy === 'role'}
|
||||
<span class="font-normal"
|
||||
>{#if direction === 'asc'}
|
||||
<ChevronUp className="size-2" />
|
||||
@@ -359,21 +368,6 @@
|
||||
<tbody class="">
|
||||
{#each users as user (user.id)}
|
||||
<tr class="bg-white dark:bg-gray-900 dark:border-gray-850 text-xs">
|
||||
<td class="px-3 py-1 min-w-[7rem] w-28">
|
||||
<button
|
||||
class=" translate-y-0.5"
|
||||
aria-label={$i18n.t('Change User Role')}
|
||||
on:click={() => {
|
||||
selectedUser = user;
|
||||
showEditUserModal = !showEditUserModal;
|
||||
}}
|
||||
>
|
||||
<Badge
|
||||
type={user.role === 'admin' ? 'info' : user.role === 'user' ? 'success' : 'muted'}
|
||||
content={$i18n.t(user.role)}
|
||||
/>
|
||||
</button>
|
||||
</td>
|
||||
<td class="px-3 py-1 font-normal text-gray-900 dark:text-white max-w-48">
|
||||
<div class="flex items-center gap-2">
|
||||
<ProfilePreview {user} side="right" align="center" sideOffset={6}>
|
||||
@@ -401,6 +395,18 @@
|
||||
{/if}
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-3 py-1 min-w-[5rem] w-20">
|
||||
<button
|
||||
class="text-xs font-normal leading-4 capitalize transition {roleClass(user.role)}"
|
||||
aria-label={$i18n.t('Change User Role')}
|
||||
on:click={() => {
|
||||
selectedUser = user;
|
||||
showEditUserModal = !showEditUserModal;
|
||||
}}
|
||||
>
|
||||
{$i18n.t(user.role)}
|
||||
</button>
|
||||
</td>
|
||||
<td class=" px-3 py-1 max-w-48 truncate"> {user.email} </td>
|
||||
|
||||
<td class=" px-3 py-1">
|
||||
|
||||
Reference in New Issue
Block a user