mirror of
https://github.com/open-webui/open-webui.git
synced 2026-09-07 08:27:05 +00:00
fix: apply admin defaults for brand-new users, normalize chatDirection, wire entry points
- +layout.svelte always merges adminDefaults with whatever user UI is
available (server-returned settings.ui, or localStorage, or {}). The
previous branch skipped defaults entirely when getUserSettings()
returned null, which is exactly the new-user case the feature
targets — admin defaults never reached users who hadn't customized
anything.
- InterfaceDefaultsForm.validate_chat_direction now accepts any casing
and normalizes to the shape the frontend store expects ('auto'
lowercase, 'LTR' / 'RTL' uppercase). The interface-settings toggle
cycles uppercase values, so admin saves with LTR/RTL were rejected
with 422 by the old lowercase-only validator.
- admin Settings/Interface.svelte adds the missing entry points: a
"Configure Defaults" button that toggles showInterfaceDefaultsModal
and a destructive "Reset All Users Interface Settings" button that
toggles showResetConfirmDialog. The flags and bound components were
already in place; nothing was flipping them.
- getInterfaceDefaults / setInterfaceDefaults / resetAllUsersInterfaceSettings
fall back through err.detail / err.message / stringification so
network-level failures (fetch TypeError, non-JSON body) surface as
thrown errors instead of resolving to null and letting callers treat
failed requests as silent successes.
This commit is contained in:
parent
6712c83cf6
commit
e7249118da
5 changed files with 70 additions and 14 deletions
|
|
@ -676,9 +676,19 @@ class InterfaceDefaultsForm(BaseModel):
|
|||
@field_validator("chatDirection")
|
||||
@classmethod
|
||||
def validate_chat_direction(cls, value):
|
||||
if value is not None and value not in ("auto", "ltr", "rtl"):
|
||||
raise ValueError("chatDirection must be 'auto', 'ltr', or 'rtl'")
|
||||
return value
|
||||
# The user-side store is typed 'LTR' | 'RTL' | 'auto' and the
|
||||
# Interface settings toggle cycles uppercase values, so accept any
|
||||
# casing and normalize to the shape the frontend expects. Rejecting
|
||||
# the uppercase values outright (as the validator did before) made
|
||||
# the modal fail with a 422 on every LTR/RTL save.
|
||||
if value is None:
|
||||
return value
|
||||
normalized = value.lower()
|
||||
if normalized == "auto":
|
||||
return "auto"
|
||||
if normalized in ("ltr", "rtl"):
|
||||
return normalized.upper()
|
||||
raise ValueError("chatDirection must be 'auto', 'LTR', or 'RTL'")
|
||||
|
||||
@field_validator("textScale")
|
||||
@classmethod
|
||||
|
|
|
|||
|
|
@ -664,7 +664,12 @@ export const getInterfaceDefaults = async (token: string) => {
|
|||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
error = err.detail;
|
||||
// Fall back through detail / message / stringification so a
|
||||
// network TypeError or non-JSON response still produces a truthy
|
||||
// error — otherwise the function silently returned null and the
|
||||
// caller couldn't tell a failed fetch from an empty-defaults
|
||||
// response.
|
||||
error = err?.detail ?? err?.message ?? (typeof err === 'string' ? err : 'Request failed');
|
||||
return null;
|
||||
});
|
||||
|
||||
|
|
@ -692,7 +697,7 @@ export const setInterfaceDefaults = async (token: string, defaults: object) => {
|
|||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
error = err.detail;
|
||||
error = err?.detail ?? err?.message ?? (typeof err === 'string' ? err : 'Request failed');
|
||||
return null;
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -567,7 +567,11 @@ export const resetAllUsersInterfaceSettings = async (token: string) => {
|
|||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
error = err.detail;
|
||||
// Fall back through detail / message / stringification so a
|
||||
// network TypeError or non-JSON response still produces a truthy
|
||||
// error — otherwise the function silently returned null and the
|
||||
// caller could mistake a failed reset for a silent success.
|
||||
error = err?.detail ?? err?.message ?? (typeof err === 'string' ? err : 'Request failed');
|
||||
return null;
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -114,6 +114,42 @@
|
|||
}}
|
||||
>
|
||||
<div class=" overflow-y-scroll scrollbar-hidden h-full pr-1.5">
|
||||
<div class="mb-3.5">
|
||||
<div class=" mt-0.5 mb-2.5 text-base font-medium">
|
||||
{$i18n.t('Interface Defaults')}
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-100/30 dark:border-gray-850/30 my-2" />
|
||||
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 mb-2">
|
||||
{$i18n.t(
|
||||
"Configure default interface settings that apply to all users who haven't customized their own."
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col sm:flex-row gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="px-3 py-1.5 text-xs font-medium rounded-lg border border-gray-200 dark:border-gray-800 hover:bg-gray-100 dark:hover:bg-gray-850 transition"
|
||||
on:click={() => {
|
||||
showInterfaceDefaultsModal = true;
|
||||
}}
|
||||
>
|
||||
{$i18n.t('Configure Defaults')}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="px-3 py-1.5 text-xs font-medium rounded-lg border border-red-300 dark:border-red-900/50 text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-950/30 transition"
|
||||
on:click={() => {
|
||||
showResetConfirmDialog = true;
|
||||
}}
|
||||
>
|
||||
{$i18n.t('Reset All Users Interface Settings')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3.5">
|
||||
<div class=" mt-0.5 mb-2.5 text-base font-medium">{$i18n.t('Tasks')}</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -929,14 +929,15 @@
|
|||
})
|
||||
]);
|
||||
|
||||
// Admin defaults are base, user settings override.
|
||||
const effectiveSettings = deepMerge(adminDefaults ?? {}, userSettings?.ui ?? {});
|
||||
|
||||
if (userSettings) {
|
||||
settings.set(effectiveSettings);
|
||||
} else {
|
||||
settings.set(JSON.parse(localStorage.getItem('settings') ?? '{}'));
|
||||
}
|
||||
// Admin defaults are the base layer; whatever the user has
|
||||
// actually customized overrides them. For brand-new users
|
||||
// getUserSettings() returns null — the whole point of admin
|
||||
// defaults is that THOSE users get them, so always merge
|
||||
// against adminDefaults instead of skipping straight to
|
||||
// localStorage when userSettings is falsy.
|
||||
const localStorageSettings = JSON.parse(localStorage.getItem('settings') ?? '{}');
|
||||
const userUI = userSettings?.ui ?? localStorageSettings ?? {};
|
||||
settings.set(deepMerge(adminDefaults ?? {}, userUI));
|
||||
setTextScale($settings?.textScale ?? 1);
|
||||
|
||||
// Set up the token expiry check
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue