diff --git a/src/lib/components/admin/Settings/Authentication.svelte b/src/lib/components/admin/Settings/Authentication.svelte index 189cb55e81..0132e15b11 100644 --- a/src/lib/components/admin/Settings/Authentication.svelte +++ b/src/lib/components/admin/Settings/Authentication.svelte @@ -92,6 +92,11 @@ toast.error(`${error}`); return null; }); + if (res?.sessions_revoked) { + localStorage.removeItem('token'); + window.location.href = '/auth?state=logout&form=signin'; + return false; + } if (res) { adminConfig = res; } @@ -99,11 +104,13 @@ }; const submitHandler = async () => { - const adminSaved = await updateAdminHandler(); const ldapSaved = await updateLdapServerHandler(); const oauthSaved = await updateOAuthHandler(); + if (!ldapSaved || !oauthSaved) return; - if (adminSaved && ldapSaved && oauthSaved) { + // Save MFA policy last because changing it revokes the current session. + const adminSaved = await updateAdminHandler(); + if (adminSaved) { toast.success($i18n.t('Settings saved successfully!')); await config.set(await getBackendConfig()); } @@ -255,6 +262,32 @@ + + + + + + {$i18n.t('Changes sign out all devices. Users enroll on their next sign-in.')} + + {#if adminConfig.ENABLE_MFA} + + + {/if} + + diff --git a/src/lib/components/admin/Settings/General.svelte b/src/lib/components/admin/Settings/General.svelte index 570d834e8c..3e269aa9e5 100644 --- a/src/lib/components/admin/Settings/General.svelte +++ b/src/lib/components/admin/Settings/General.svelte @@ -275,31 +275,7 @@ {/if} - - - - - - {$i18n.t('Changes sign out all devices. Users enroll on their next sign-in.')} - - {#if adminConfig.ENABLE_MFA} - - - {/if} - + { showRevokeConfirmation = true; }}>{$i18n.t('Sign out all devices')} {:else} - + {$i18n.t( challenge.next_step === 'enroll' ? 'Set up your authenticator' @@ -103,7 +103,7 @@ /> {$i18n.t('Enter the key manually')} @@ -118,11 +118,11 @@ {:else}{$i18n.t('Retry setup')}{/if} {/if} - + {$i18n.t( challenge.next_step === 'recover' ? 'Operator recovery token' @@ -156,10 +156,10 @@ {#if error} {error} {/if} - + {$i18n.t(busy ? 'Verifying…' : 'Continue')} @@ -169,13 +169,13 @@ > {$i18n.t('Back to sign in')} {#if challenge.next_step === 'verify'} { recovery = !recovery; diff --git a/src/lib/components/auth/MfaRecoveryCodes.svelte b/src/lib/components/auth/MfaRecoveryCodes.svelte index fc55b3e4af..8da0450372 100644 --- a/src/lib/components/auth/MfaRecoveryCodes.svelte +++ b/src/lib/components/auth/MfaRecoveryCodes.svelte @@ -17,7 +17,7 @@ - + {$i18n.t('Save your recovery codes')} @@ -31,7 +31,7 @@ {$i18n.t('Download recovery codes')} {$i18n.t('I have saved my recovery codes')} - + {$i18n.t('Continue')} diff --git a/src/lib/components/chat/Settings/Account/Mfa.svelte b/src/lib/components/chat/Settings/Account/Mfa.svelte index 3d0eb846a5..37174e261f 100644 --- a/src/lib/components/chat/Settings/Account/Mfa.svelte +++ b/src/lib/components/chat/Settings/Account/Mfa.svelte @@ -15,7 +15,7 @@ let reauthenticate = false; let show = false; const actionButtonClass = - 'text-xs text-gray-500 transition-colors hover:text-gray-900 disabled:opacity-40 dark:text-gray-500 dark:hover:text-white'; + 'text-xs text-gray-500 transition-colors hover:text-gray-700 disabled:opacity-40 dark:text-gray-500 dark:hover:text-gray-200'; onMount(async () => { try { status = await mfaRequest('status', undefined, localStorage.token); diff --git a/src/lib/components/workspace/Models.svelte b/src/lib/components/workspace/Models.svelte index d578c4f654..12f8533488 100644 --- a/src/lib/components/workspace/Models.svelte +++ b/src/lib/components/workspace/Models.svelte @@ -46,6 +46,7 @@ } from '$lib/utils/localizedContent'; import EllipsisHorizontal from '../icons/EllipsisHorizontal.svelte'; + import ArrowRight from '../icons/ArrowRight.svelte'; import CheckCircle from '../icons/CheckCircle.svelte'; import Minus from '../icons/Minus.svelte'; import ModelMenu from './Models/ModelMenu.svelte'; @@ -80,6 +81,7 @@ let showModelDeleteConfirm = false; let selectedModel = null; + let updatingModels: Record = {}; let groupIds = []; @@ -151,6 +153,43 @@ return target instanceof Element && !!target.closest('button, a, input, [role="menu"]'); }; + const confirmDeleteModel = (model: any) => { + selectedModel = model; + showModelDeleteConfirm = true; + }; + + const toggleModelHandler = async (model: any) => { + if (updatingModels[model.id]) return; + const previousState = !model.is_active; + updatingModels = { ...updatingModels, [model.id]: true }; + + try { + const updatedModel = await toggleModelById(localStorage.token, model.id); + model.is_active = updatedModel.is_active; + models = models; + } catch (error: any) { + model.is_active = previousState; + models = models; + toast.error(`${error?.detail ?? error}`); + return; + } finally { + updatingModels = { ...updatingModels, [model.id]: false }; + } + + try { + _models.set( + await getModels( + localStorage.token, + $config?.features?.enable_direct_connections + ? ($settings?.directConnections ?? null) + : null + ) + ); + } catch (error: any) { + toast.error(`${error?.detail ?? error}`); + } + }; + const getModelList = async () => { if (!loaded) return; @@ -662,11 +701,10 @@ {#if models !== null} {#if (models ?? []).length !== 0} - + + {$i18n.t('Sort by')} setSortKey('name')} > @@ -680,10 +718,8 @@ {/if} - - setSortKey('updated_at')} > @@ -702,7 +738,7 @@ {#each models as model (model.id)} {@const localizedModelName = resolveLocalizedModelName(model, $i18n.language)} - + - + + {#if model.write_access} + {localizedModelName} + {:else} + {localizedModelName} + {/if} + + - {localizedModelName} - - - + {dayjs(model.updated_at * 1000).fromNow()} - {dayjs(model.updated_at * 1000).fromNow()} - {#if !model.write_access} @@ -811,7 +866,7 @@ - + {#if shiftKey && model.write_access} { e.preventDefault(); e.stopPropagation(); - deleteModelHandler(model); + confirmDeleteModel(model); }} > {:else} - + { - selectedModel = model; - showModelDeleteConfirm = true; + confirmDeleteModel(model); }} onClose={() => {}} > @@ -887,32 +941,20 @@ {#if model.write_access} - { - e.stopPropagation(); - e.preventDefault(); - }} + - - { - toggleModelById(localStorage.token, model.id); - _models.set( - await getModels( - localStorage.token, - $config?.features?.enable_direct_connections && - ($settings?.directConnections ?? null) - ) - ); - }} - /> - - + toggleModelHandler(model)} + /> + {/if} {/if} diff --git a/src/lib/components/workspace/Models/ModelMenu.svelte b/src/lib/components/workspace/Models/ModelMenu.svelte index e44307776d..9e134e9d59 100644 --- a/src/lib/components/workspace/Models/ModelMenu.svelte +++ b/src/lib/components/workspace/Models/ModelMenu.svelte @@ -17,8 +17,9 @@ import { config, user as currentUser, pinnedModels, settings } from '$lib/stores'; import Link from '$lib/components/icons/Link.svelte'; + import { resolveLocalizedModelName } from '$lib/utils/localizedContent'; - const i18n = getContext('i18n'); + const i18n = getContext('i18n'); export let user; export let model; @@ -54,6 +55,11 @@ > { e.stopPropagation(); show = !show; diff --git a/src/lib/i18n/locales/en-US/translation.json b/src/lib/i18n/locales/en-US/translation.json index 018258aedf..d961590bc4 100644 --- a/src/lib/i18n/locales/en-US/translation.json +++ b/src/lib/i18n/locales/en-US/translation.json @@ -3128,6 +3128,7 @@ "Sonar Pro": "", "Sonar Reasoning": "", "Sonar Reasoning Pro": "", + "Sort by": "", "Sort": "", "Source": "", "Specific users or groups": "", @@ -3326,6 +3327,7 @@ "Transformers": "", "Translation key": "", "Trial License": "", + "Try in chat": "", "Try adjusting your search or filter to find what you are looking for.": "", "Try Again": "", "TTS Voice": "", diff --git a/src/routes/auth/+page.svelte b/src/routes/auth/+page.svelte index cca615a041..07474cc1e7 100644 --- a/src/routes/auth/+page.svelte +++ b/src/routes/auth/+page.svelte @@ -274,7 +274,9 @@ /> {:else if pendingApproval} - {$i18n.t('Your account is awaiting administrator approval.')} + + {$i18n.t('Your account is awaiting administrator approval.')} + {:else if ($config?.features.auth_trusted_header ?? false) || $config?.features.auth === false}
+ {$i18n.t('Changes sign out all devices. Users enroll on their next sign-in.')} +
- {$i18n.t('Changes sign out all devices. Users enroll on their next sign-in.')} -
{error}
@@ -31,7 +31,7 @@ {$i18n.t('Download recovery codes')} {$i18n.t('I have saved my recovery codes')} -
{$i18n.t('Your account is awaiting administrator approval.')}
+ {$i18n.t('Your account is awaiting administrator approval.')} +