From ecfa0b8c47dc1b002edad9f225d5ea4d3479b431 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Fri, 23 Oct 2026 10:55:57 -0400 Subject: [PATCH 1/5] =?UTF-8?q?=F0=9F=A7=AD=20feat:=20Gate=20Config=20Beha?= =?UTF-8?q?viour=20on=20the=20Server's=20Admin=20Config=20Contract=20and?= =?UTF-8?q?=20Accept=20Dotted=20Keys?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a gating layer that reads the admin config contract (GET /api/admin/config/meta) with bundled fallbacks for older servers, accepts dotted record keys where the server keeps them, and fixes a set of smaller configuration issues. --- src/components/access/EditRoleDialog.tsx | 54 ++++- .../access/__tests__/EditRoleDialog.test.tsx | 103 +++++++++ .../configuration/ManagedNotice.tsx | 14 +- src/components/configuration/edits.test.ts | 40 +++- .../configuration/fields/KeyValueField.tsx | 11 +- .../configuration/fields/ObjectEntryCard.tsx | 13 +- .../fields/RecordObjectField.tsx | 14 +- .../fields/__tests__/collections.test.tsx | 70 +++++++ src/components/configuration/import.test.ts | 117 +++++++++++ src/components/configuration/import.ts | 45 +++- src/components/configuration/issues.ts | 12 +- .../configuration/renderer/FieldRenderer.tsx | 70 +++++-- .../renderer/__tests__/controls.test.tsx | 58 +++++ src/components/configuration/review/diff.ts | 52 ++++- src/components/configuration/review/revert.ts | 19 +- .../configuration/review/review.test.ts | 97 +++++++++ .../sections/LangfuseRenderer.tsx | 10 +- .../__tests__/LangfuseRenderer.test.tsx | 30 +-- .../sections/features/Credential.tsx | 22 +- .../features/__tests__/WebSearch.test.tsx | 39 +++- .../sections/features/__tests__/harness.tsx | 20 +- .../configuration/sections/mcp/Create.tsx | 31 ++- .../configuration/sections/mcp/Editor.tsx | 14 +- .../configuration/sections/mcp/Servers.tsx | 13 +- .../sections/mcp/__tests__/Create.test.tsx | 85 ++++++++ .../sections/mcp/__tests__/Servers.test.tsx | 198 ++++++++++++++++-- .../sections/mcp/__tests__/logic.test.ts | 9 + .../configuration/sections/mcp/list.ts | 10 +- .../configuration/sections/mcp/names.ts | 7 +- .../configuration/sections/mcp/transport.ts | 9 +- src/components/users/UsersPage.tsx | 11 +- src/constants/config.ts | 64 ++++-- src/constants/contract.ts | 127 +++++++++++ src/constants/index.ts | 1 + src/contexts/ContractContext.tsx | 22 ++ src/hooks/config/contract.ts | 31 +++ src/hooks/config/import.test.tsx | 189 ++++++++++++++++- src/hooks/config/import.ts | 97 ++++++--- src/hooks/config/index.ts | 1 + src/hooks/config/save.ts | 21 +- src/locales/en/translation.json | 20 ++ src/routes/_app.tsx | 21 +- src/server/config.test.ts | 41 +++- src/server/config/index.ts | 1 + src/server/config/meta.test.ts | 101 +++++++++ src/server/config/meta.ts | 96 +++++++++ src/server/config/save.test.ts | 78 +++++++ src/server/config/save.ts | 78 ++++--- src/server/config/schema.ts | 19 +- src/server/langfuse.test.ts | 2 +- src/server/langfuse.ts | 24 +-- src/server/users.test.ts | 54 +++++ src/server/users.ts | 21 +- src/test/admin.ts | 8 + src/test/page.tsx | 7 +- src/types/access.ts | 9 + src/types/changes.ts | 8 + src/types/config-ui.ts | 7 + src/types/contract.ts | 132 ++++++++++++ src/types/index.ts | 1 + src/types/review.ts | 16 +- src/utils/changes.test.ts | 142 +++++++++++-- src/utils/changes.ts | 97 ++++++--- src/utils/collections.ts | 4 +- src/utils/contract.test.ts | 80 +++++++ src/utils/contract.ts | 44 ++++ src/utils/format.test.ts | 27 ++- src/utils/format.ts | 15 +- src/utils/index.ts | 1 + src/utils/keys.test.ts | 27 +++ src/utils/keys.ts | 31 ++- src/utils/leaves.ts | 104 +++++++-- src/utils/paths.test.ts | 10 + src/utils/paths.ts | 11 +- src/utils/validation.ts | 42 ++++ src/utils/values.ts | 4 +- 76 files changed, 2892 insertions(+), 341 deletions(-) create mode 100644 src/components/access/__tests__/EditRoleDialog.test.tsx create mode 100644 src/components/configuration/sections/mcp/__tests__/Create.test.tsx create mode 100644 src/constants/contract.ts create mode 100644 src/contexts/ContractContext.tsx create mode 100644 src/hooks/config/contract.ts create mode 100644 src/server/config/meta.test.ts create mode 100644 src/server/config/meta.ts create mode 100644 src/server/users.test.ts create mode 100644 src/types/contract.ts create mode 100644 src/utils/contract.test.ts create mode 100644 src/utils/contract.ts diff --git a/src/components/access/EditRoleDialog.tsx b/src/components/access/EditRoleDialog.tsx index 3ff20e28..f2b31292 100644 --- a/src/components/access/EditRoleDialog.tsx +++ b/src/components/access/EditRoleDialog.tsx @@ -1,3 +1,4 @@ +import { PrincipalType } from 'librechat-data-provider'; import { useState, useEffect, useCallback } from 'react'; import { Button, Dialog, Tabs } from '@clickhouse/click-ui'; import { useQuery, useMutation, useQueryClient, keepPreviousData } from '@tanstack/react-query'; @@ -8,6 +9,7 @@ import { removeRoleMemberFn, roleQueryOptions, roleMembersQueryOptions, + availableScopesOptions, updateRoleFn, updateRolePermissionsFn, MEMBERS_PAGE_SIZE, @@ -16,13 +18,14 @@ import { Avatar, LoadingState, Pagination, + DecorativeIcon, SelectedMemberList, TrashButton, UserSearchInline, } from '@/components/shared'; import { RolePermissionsPanel } from './RolePermissionsPanel'; +import { useLocalize, useConfigFeatures } from '@/hooks'; import { cn, notifySuccess, notifyError } from '@/utils'; -import { useLocalize } from '@/hooks'; type EditRoleTab = 'details' | 'permissions' | 'members'; @@ -44,6 +47,15 @@ export function EditRoleDialog({ role, canManage, onClose }: t.EditRoleDialogPro enabled: !!role, }); + const { roleRenameMovesProfile } = useConfigFeatures(); + const { data: scopes } = useQuery({ ...availableScopesOptions, enabled: !!role }); + const hasProfile = + role != null && + (scopes ?? []).some( + (scope) => scope.principalType === PrincipalType.ROLE && scope.principalId === role.name, + ); + const isRenaming = role != null && name.trim() !== '' && name.trim() !== role.name; + useEffect(() => { if (roleDetail.data && permissions === null) { setPermissions(roleDetail.data.permissions); @@ -147,6 +159,9 @@ export function EditRoleDialog({ role, canManage, onClose }: t.EditRoleDialogPro if (data.roleId !== role?.id) { queryClient.invalidateQueries({ queryKey: ['roleMembers', data.roleId] }); } + if (hasProfile && data.name !== role?.name) { + queryClient.invalidateQueries({ queryKey: ['availableScopes'] }); + } notifySuccess(localize('com_toast_role_updated', { name: data.name })); onClose(); }, @@ -223,8 +238,18 @@ export function EditRoleDialog({ role, canManage, onClose }: t.EditRoleDialogPro disabled={role?.isSystemRole || !canManage} readOnly={!canManage} autoFocus + aria-describedby={ + isRenaming && hasProfile ? 'edit-role-rename-note' : undefined + } className="rounded-lg border border-(--cui-color-stroke-default) bg-(--cui-color-background-default) px-3 py-2 text-sm text-(--cui-color-text-default) placeholder:text-(--cui-color-text-disabled) disabled:cursor-not-allowed disabled:opacity-50" /> + {isRenaming && hasProfile && ( + + )}