🧑‍🤝‍🧑 feat: Add People Picker Permissions Management UI

This commit is contained in:
Danny Avila 2025-08-10 17:42:33 -04:00
parent d82a63642d
commit a434d28579
No known key found for this signature in database
GPG key ID: BF31EEB2C5CA0956
15 changed files with 419 additions and 229 deletions

View file

@ -3,6 +3,7 @@ const {
promptPermissionsSchema,
memoryPermissionsSchema,
agentPermissionsSchema,
peoplePickerPermissionsSchema,
PermissionTypes,
roleDefaults,
SystemRoles,
@ -13,6 +14,76 @@ const { updateRoleByName, getRoleByName } = require('~/models/Role');
const router = express.Router();
router.use(requireJwtAuth);
/**
* Permission configuration mapping
* Maps route paths to their corresponding schemas and permission types
*/
const permissionConfigs = {
prompts: {
schema: promptPermissionsSchema,
permissionType: PermissionTypes.PROMPTS,
errorMessage: 'Invalid prompt permissions.',
},
agents: {
schema: agentPermissionsSchema,
permissionType: PermissionTypes.AGENTS,
errorMessage: 'Invalid agent permissions.',
},
memories: {
schema: memoryPermissionsSchema,
permissionType: PermissionTypes.MEMORIES,
errorMessage: 'Invalid memory permissions.',
},
'people-picker': {
schema: peoplePickerPermissionsSchema,
permissionType: PermissionTypes.PEOPLE_PICKER,
errorMessage: 'Invalid people picker permissions.',
},
};
/**
* Generic handler for updating permissions
* @param {string} permissionKey - The key from permissionConfigs
* @returns {Function} Express route handler
*/
const createPermissionUpdateHandler = (permissionKey) => {
const config = permissionConfigs[permissionKey];
return async (req, res) => {
const { roleName: _r } = req.params;
// TODO: TEMP, use a better parsing for roleName
const roleName = _r.toUpperCase();
const updates = req.body;
try {
const parsedUpdates = config.schema.partial().parse(updates);
const role = await getRoleByName(roleName);
if (!role) {
return res.status(404).send({ message: 'Role not found' });
}
const currentPermissions =
role.permissions?.[config.permissionType] || role[config.permissionType] || {};
const mergedUpdates = {
permissions: {
...role.permissions,
[config.permissionType]: {
...currentPermissions,
...parsedUpdates,
},
},
};
const updatedRole = await updateRoleByName(roleName, mergedUpdates);
res.status(200).send(updatedRole);
} catch (error) {
return res.status(400).send({ message: config.errorMessage, error: error.errors });
}
};
};
/**
* GET /api/roles/:roleName
* Get a specific role by name
@ -45,117 +116,24 @@ router.get('/:roleName', async (req, res) => {
* PUT /api/roles/:roleName/prompts
* Update prompt permissions for a specific role
*/
router.put('/:roleName/prompts', checkAdmin, async (req, res) => {
const { roleName: _r } = req.params;
// TODO: TEMP, use a better parsing for roleName
const roleName = _r.toUpperCase();
/** @type {TRole['permissions']['PROMPTS']} */
const updates = req.body;
try {
const parsedUpdates = promptPermissionsSchema.partial().parse(updates);
const role = await getRoleByName(roleName);
if (!role) {
return res.status(404).send({ message: 'Role not found' });
}
const currentPermissions =
role.permissions?.[PermissionTypes.PROMPTS] || role[PermissionTypes.PROMPTS] || {};
const mergedUpdates = {
permissions: {
...role.permissions,
[PermissionTypes.PROMPTS]: {
...currentPermissions,
...parsedUpdates,
},
},
};
const updatedRole = await updateRoleByName(roleName, mergedUpdates);
res.status(200).send(updatedRole);
} catch (error) {
return res.status(400).send({ message: 'Invalid prompt permissions.', error: error.errors });
}
});
router.put('/:roleName/prompts', checkAdmin, createPermissionUpdateHandler('prompts'));
/**
* PUT /api/roles/:roleName/agents
* Update agent permissions for a specific role
*/
router.put('/:roleName/agents', checkAdmin, async (req, res) => {
const { roleName: _r } = req.params;
// TODO: TEMP, use a better parsing for roleName
const roleName = _r.toUpperCase();
/** @type {TRole['permissions']['AGENTS']} */
const updates = req.body;
try {
const parsedUpdates = agentPermissionsSchema.partial().parse(updates);
const role = await getRoleByName(roleName);
if (!role) {
return res.status(404).send({ message: 'Role not found' });
}
const currentPermissions =
role.permissions?.[PermissionTypes.AGENTS] || role[PermissionTypes.AGENTS] || {};
const mergedUpdates = {
permissions: {
...role.permissions,
[PermissionTypes.AGENTS]: {
...currentPermissions,
...parsedUpdates,
},
},
};
const updatedRole = await updateRoleByName(roleName, mergedUpdates);
res.status(200).send(updatedRole);
} catch (error) {
return res.status(400).send({ message: 'Invalid agent permissions.', error: error.errors });
}
});
router.put('/:roleName/agents', checkAdmin, createPermissionUpdateHandler('agents'));
/**
* PUT /api/roles/:roleName/memories
* Update memory permissions for a specific role
*/
router.put('/:roleName/memories', checkAdmin, async (req, res) => {
const { roleName: _r } = req.params;
// TODO: TEMP, use a better parsing for roleName
const roleName = _r.toUpperCase();
/** @type {TRole['permissions']['MEMORIES']} */
const updates = req.body;
router.put('/:roleName/memories', checkAdmin, createPermissionUpdateHandler('memories'));
try {
const parsedUpdates = memoryPermissionsSchema.partial().parse(updates);
const role = await getRoleByName(roleName);
if (!role) {
return res.status(404).send({ message: 'Role not found' });
}
const currentPermissions =
role.permissions?.[PermissionTypes.MEMORIES] || role[PermissionTypes.MEMORIES] || {};
const mergedUpdates = {
permissions: {
...role.permissions,
[PermissionTypes.MEMORIES]: {
...currentPermissions,
...parsedUpdates,
},
},
};
const updatedRole = await updateRoleByName(roleName, mergedUpdates);
res.status(200).send(updatedRole);
} catch (error) {
return res.status(400).send({ message: 'Invalid memory permissions.', error: error.errors });
}
});
/**
* PUT /api/roles/:roleName/people-picker
* Update people picker permissions for a specific role
*/
router.put('/:roleName/people-picker', checkAdmin, createPermissionUpdateHandler('people-picker'));
module.exports = router;

View file

@ -94,109 +94,71 @@ describe('AppService interface configuration', () => {
mockLoadCustomConfig.mockResolvedValue({
interface: {
peoplePicker: {
admin: {
users: true,
groups: true,
roles: true,
},
user: {
users: false,
groups: false,
roles: false,
},
users: true,
groups: true,
roles: true,
},
},
});
loadDefaultInterface.mockResolvedValue({
peoplePicker: {
admin: {
users: true,
groups: true,
roles: true,
},
user: {
users: false,
groups: false,
roles: false,
},
users: true,
groups: true,
roles: true,
},
});
await AppService(app);
expect(app.locals.interfaceConfig.peoplePicker).toBeDefined();
expect(app.locals.interfaceConfig.peoplePicker.admin).toMatchObject({
expect(app.locals.interfaceConfig.peoplePicker).toMatchObject({
users: true,
groups: true,
roles: true,
});
expect(app.locals.interfaceConfig.peoplePicker.user).toMatchObject({
users: false,
groups: false,
roles: false,
});
expect(loadDefaultInterface).toHaveBeenCalled();
});
it('should handle mixed peoplePicker permissions for roles', async () => {
it('should handle mixed peoplePicker permissions', async () => {
mockLoadCustomConfig.mockResolvedValue({
interface: {
peoplePicker: {
admin: {
users: true,
groups: true,
roles: false,
},
user: {
users: true,
groups: false,
roles: true,
},
users: true,
groups: false,
roles: true,
},
},
});
loadDefaultInterface.mockResolvedValue({
peoplePicker: {
admin: {
users: true,
groups: true,
roles: false,
},
user: {
users: true,
groups: false,
roles: true,
},
users: true,
groups: false,
roles: true,
},
});
await AppService(app);
expect(app.locals.interfaceConfig.peoplePicker.admin.roles).toBe(false);
expect(app.locals.interfaceConfig.peoplePicker.user.roles).toBe(true);
expect(app.locals.interfaceConfig.peoplePicker.users).toBe(true);
expect(app.locals.interfaceConfig.peoplePicker.groups).toBe(false);
expect(app.locals.interfaceConfig.peoplePicker.roles).toBe(true);
});
it('should set default peoplePicker roles permissions when not provided', async () => {
it('should set default peoplePicker permissions when not provided', async () => {
mockLoadCustomConfig.mockResolvedValue({});
loadDefaultInterface.mockResolvedValue({
peoplePicker: {
admin: {
users: true,
groups: true,
roles: true,
},
user: {
users: false,
groups: false,
roles: false,
},
users: true,
groups: true,
roles: true,
},
});
await AppService(app);
expect(app.locals.interfaceConfig.peoplePicker).toBeDefined();
expect(app.locals.interfaceConfig.peoplePicker.admin.roles).toBe(true);
expect(app.locals.interfaceConfig.peoplePicker.user.roles).toBe(false);
expect(app.locals.interfaceConfig.peoplePicker.users).toBe(true);
expect(app.locals.interfaceConfig.peoplePicker.groups).toBe(true);
expect(app.locals.interfaceConfig.peoplePicker.roles).toBe(true);
});
});

View file

@ -970,20 +970,13 @@ describe('AppService updating app.locals and issuing warnings', () => {
expect(app.locals.ocr.mistralModel).toEqual('mistral-medium');
});
it('should correctly configure peoplePicker with roles permission when specified', async () => {
it('should correctly configure peoplePicker permissions when specified', async () => {
const mockConfig = {
interface: {
peoplePicker: {
admin: {
users: true,
groups: true,
roles: true,
},
user: {
users: false,
groups: false,
roles: true,
},
users: true,
groups: true,
roles: true,
},
},
};
@ -993,21 +986,16 @@ describe('AppService updating app.locals and issuing warnings', () => {
const app = { locals: {} };
await AppService(app);
// Check that interface config includes the roles permission
// Check that interface config includes the permissions
expect(app.locals.interfaceConfig.peoplePicker).toBeDefined();
expect(app.locals.interfaceConfig.peoplePicker.admin).toMatchObject({
expect(app.locals.interfaceConfig.peoplePicker).toMatchObject({
users: true,
groups: true,
roles: true,
});
expect(app.locals.interfaceConfig.peoplePicker.user).toMatchObject({
users: false,
groups: false,
roles: true,
});
});
it('should use default peoplePicker roles permissions when not specified', async () => {
it('should use default peoplePicker permissions when not specified', async () => {
const mockConfig = {
interface: {
// No peoplePicker configuration
@ -1019,9 +1007,10 @@ describe('AppService updating app.locals and issuing warnings', () => {
const app = { locals: {} };
await AppService(app);
// Check that default roles permissions are applied
// Check that default permissions are applied
expect(app.locals.interfaceConfig.peoplePicker).toBeDefined();
expect(app.locals.interfaceConfig.peoplePicker.admin.roles).toBe(true);
expect(app.locals.interfaceConfig.peoplePicker.user.roles).toBe(false);
expect(app.locals.interfaceConfig.peoplePicker.users).toBe(true);
expect(app.locals.interfaceConfig.peoplePicker.groups).toBe(true);
expect(app.locals.interfaceConfig.peoplePicker.roles).toBe(true);
});
});

View file

@ -54,16 +54,9 @@ async function loadDefaultInterface(config, configDefaults, roleName = SystemRol
fileCitations: interfaceConfig?.fileCitations ?? defaults.fileCitations,
customWelcome: interfaceConfig?.customWelcome ?? defaults.customWelcome,
peoplePicker: {
admin: {
users: interfaceConfig?.peoplePicker?.admin?.users ?? defaults.peoplePicker?.admin.users,
groups: interfaceConfig?.peoplePicker?.admin?.groups ?? defaults.peoplePicker?.admin.groups,
roles: interfaceConfig?.peoplePicker?.admin?.roles ?? defaults.peoplePicker?.admin.roles,
},
user: {
users: interfaceConfig?.peoplePicker?.user?.users ?? defaults.peoplePicker?.user.users,
groups: interfaceConfig?.peoplePicker?.user?.groups ?? defaults.peoplePicker?.user.groups,
roles: interfaceConfig?.peoplePicker?.user?.roles ?? defaults.peoplePicker?.user.roles,
},
users: interfaceConfig?.peoplePicker?.users ?? defaults.peoplePicker?.users,
groups: interfaceConfig?.peoplePicker?.groups ?? defaults.peoplePicker?.groups,
roles: interfaceConfig?.peoplePicker?.roles ?? defaults.peoplePicker?.roles,
},
marketplace: {
admin: {
@ -88,9 +81,12 @@ async function loadDefaultInterface(config, configDefaults, roleName = SystemRol
[PermissionTypes.RUN_CODE]: { [Permissions.USE]: loadedInterface.runCode },
[PermissionTypes.WEB_SEARCH]: { [Permissions.USE]: loadedInterface.webSearch },
[PermissionTypes.PEOPLE_PICKER]: {
[Permissions.VIEW_USERS]: loadedInterface.peoplePicker.user?.users,
[Permissions.VIEW_GROUPS]: loadedInterface.peoplePicker.user?.groups,
[Permissions.VIEW_ROLES]: loadedInterface.peoplePicker.user?.roles,
[Permissions.VIEW_USERS]:
roleName === SystemRoles.USER ? false : loadedInterface.peoplePicker?.users,
[Permissions.VIEW_GROUPS]:
roleName === SystemRoles.USER ? false : loadedInterface.peoplePicker?.groups,
[Permissions.VIEW_ROLES]:
roleName === SystemRoles.USER ? false : loadedInterface.peoplePicker?.roles,
},
[PermissionTypes.MARKETPLACE]: {
[Permissions.USE]: loadedInterface.marketplace.user?.use,
@ -111,9 +107,9 @@ async function loadDefaultInterface(config, configDefaults, roleName = SystemRol
[PermissionTypes.RUN_CODE]: { [Permissions.USE]: loadedInterface.runCode },
[PermissionTypes.WEB_SEARCH]: { [Permissions.USE]: loadedInterface.webSearch },
[PermissionTypes.PEOPLE_PICKER]: {
[Permissions.VIEW_USERS]: loadedInterface.peoplePicker.admin?.users,
[Permissions.VIEW_GROUPS]: loadedInterface.peoplePicker.admin?.groups,
[Permissions.VIEW_ROLES]: loadedInterface.peoplePicker.admin?.roles,
[Permissions.VIEW_USERS]: loadedInterface.peoplePicker?.users,
[Permissions.VIEW_GROUPS]: loadedInterface.peoplePicker?.groups,
[Permissions.VIEW_ROLES]: loadedInterface.peoplePicker?.roles,
},
[PermissionTypes.MARKETPLACE]: {
[Permissions.USE]: loadedInterface.marketplace.admin?.use,

View file

@ -153,7 +153,7 @@ const AdminSettings = () => {
<span className="hidden sm:flex">{localize('com_ui_admin')}</span>
</Button>
</OGDialogTrigger>
<OGDialogContent className="w-11/12 max-w-lg border-border-light bg-surface-primary text-text-primary">
<OGDialogContent className="max-w-lg border-border-light bg-surface-primary text-text-primary md:w-1/4">
<OGDialogTitle>
{`${localize('com_ui_admin_settings')} - ${localize('com_ui_prompts')}`}
</OGDialogTitle>

View file

@ -21,6 +21,7 @@ import {
useLocalize,
} from '~/hooks';
import UnifiedPeopleSearch from './PeoplePicker/UnifiedPeopleSearch';
import PeoplePickerAdminSettings from './PeoplePickerAdminSettings';
import PublicSharingToggle from './PublicSharingToggle';
import { SelectedPrincipalsList } from './PeoplePicker';
import { cn } from '~/utils';
@ -366,7 +367,8 @@ export default function GenericGrantAccessDialog({
</Button>
)}
</div>
<div className="flex gap-3">
<div className="flex gap-2">
<PeoplePickerAdminSettings />
<OGDialogClose asChild>
<Button variant="outline" onClick={handleCancel}>
{localize('com_ui_cancel')}

View file

@ -0,0 +1,221 @@
import { useMemo, useEffect, useState } from 'react';
import * as Ariakit from '@ariakit/react';
import { ShieldEllipsis } from 'lucide-react';
import { useForm, Controller } from 'react-hook-form';
import { Permissions, SystemRoles, roleDefaults, PermissionTypes } from 'librechat-data-provider';
import {
Button,
Switch,
OGDialog,
DropdownPopup,
OGDialogTitle,
OGDialogContent,
OGDialogTrigger,
useToastContext,
} from '@librechat/client';
import type { Control, UseFormSetValue, UseFormGetValues } from 'react-hook-form';
import { useUpdatePeoplePickerPermissionsMutation } from '~/data-provider';
import { useLocalize, useAuthContext } from '~/hooks';
type FormValues = {
[Permissions.VIEW_USERS]: boolean;
[Permissions.VIEW_GROUPS]: boolean;
[Permissions.VIEW_ROLES]: boolean;
};
type LabelControllerProps = {
label: string;
peoplePickerPerm: Permissions.VIEW_USERS | Permissions.VIEW_GROUPS | Permissions.VIEW_ROLES;
control: Control<FormValues, unknown, FormValues>;
setValue: UseFormSetValue<FormValues>;
getValues: UseFormGetValues<FormValues>;
};
const LabelController: React.FC<LabelControllerProps> = ({
control,
peoplePickerPerm,
label,
getValues,
setValue,
}) => (
<div className="mb-4 flex items-center justify-between gap-2">
<button
className="cursor-pointer select-none"
type="button"
onClick={() =>
setValue(peoplePickerPerm, !getValues(peoplePickerPerm), {
shouldDirty: true,
})
}
tabIndex={0}
>
{label}
</button>
<Controller
name={peoplePickerPerm}
control={control}
render={({ field }) => (
<Switch
{...field}
checked={field.value}
onCheckedChange={field.onChange}
value={field.value.toString()}
/>
)}
/>
</div>
);
const PeoplePickerAdminSettings = () => {
const localize = useLocalize();
const { showToast } = useToastContext();
const { user, roles } = useAuthContext();
const { mutate, isLoading } = useUpdatePeoplePickerPermissionsMutation({
onSuccess: () => {
showToast({ status: 'success', message: localize('com_ui_saved') });
},
onError: () => {
showToast({ status: 'error', message: localize('com_ui_error_save_admin_settings') });
},
});
const [isRoleMenuOpen, setIsRoleMenuOpen] = useState(false);
const [selectedRole, setSelectedRole] = useState<SystemRoles>(SystemRoles.USER);
const defaultValues = useMemo(() => {
const rolePerms = roles?.[selectedRole]?.permissions;
if (rolePerms) {
return rolePerms[PermissionTypes.PEOPLE_PICKER];
}
return roleDefaults[selectedRole].permissions[PermissionTypes.PEOPLE_PICKER];
}, [roles, selectedRole]);
const {
reset,
control,
setValue,
getValues,
handleSubmit,
formState: { isSubmitting },
} = useForm<FormValues>({
mode: 'onChange',
defaultValues,
});
useEffect(() => {
const value = roles?.[selectedRole]?.permissions?.[PermissionTypes.PEOPLE_PICKER];
if (value) {
reset(value);
} else {
reset(roleDefaults[selectedRole].permissions[PermissionTypes.PEOPLE_PICKER]);
}
}, [roles, selectedRole, reset]);
if (user?.role !== SystemRoles.ADMIN) {
return null;
}
const labelControllerData: {
peoplePickerPerm: Permissions.VIEW_USERS | Permissions.VIEW_GROUPS | Permissions.VIEW_ROLES;
label: string;
}[] = [
{
peoplePickerPerm: Permissions.VIEW_USERS,
label: localize('com_ui_people_picker_allow_view_users'),
},
{
peoplePickerPerm: Permissions.VIEW_GROUPS,
label: localize('com_ui_people_picker_allow_view_groups'),
},
{
peoplePickerPerm: Permissions.VIEW_ROLES,
label: localize('com_ui_people_picker_allow_view_roles'),
},
];
const onSubmit = (data: FormValues) => {
mutate({ roleName: selectedRole, updates: data });
};
const roleDropdownItems = [
{
label: SystemRoles.USER,
onClick: () => {
setSelectedRole(SystemRoles.USER);
},
},
{
label: SystemRoles.ADMIN,
onClick: () => {
setSelectedRole(SystemRoles.ADMIN);
},
},
];
return (
<OGDialog>
<OGDialogTrigger asChild>
<Button
variant={'outline'}
className="btn btn-neutral border-token-border-light relative gap-1 rounded-lg font-medium"
>
<ShieldEllipsis className="cursor-pointer" aria-hidden="true" />
{localize('com_ui_admin_settings')}
</Button>
</OGDialogTrigger>
<OGDialogContent className="w-full border-border-light bg-surface-primary text-text-primary md:w-1/4">
<OGDialogTitle>{`${localize('com_ui_admin_settings')} - ${localize(
'com_ui_people_picker',
)}`}</OGDialogTitle>
<div className="p-2">
{/* Role selection dropdown */}
<div className="flex items-center gap-2">
<span className="font-medium">{localize('com_ui_role_select')}:</span>
<DropdownPopup
unmountOnHide={true}
menuId="role-dropdown"
isOpen={isRoleMenuOpen}
setIsOpen={setIsRoleMenuOpen}
trigger={
<Ariakit.MenuButton className="inline-flex w-1/4 items-center justify-center rounded-lg border border-border-light bg-transparent px-2 py-1 text-text-primary transition-all ease-in-out hover:bg-surface-tertiary">
{selectedRole}
</Ariakit.MenuButton>
}
items={roleDropdownItems}
itemClassName="items-center justify-center"
sameWidth={true}
/>
</div>
{/* Permissions form */}
<form onSubmit={handleSubmit(onSubmit)}>
<div className="py-5">
{labelControllerData.map(({ peoplePickerPerm, label }) => (
<div key={peoplePickerPerm}>
<LabelController
control={control}
peoplePickerPerm={peoplePickerPerm}
label={label}
getValues={getValues}
setValue={setValue}
/>
</div>
))}
</div>
<div className="flex justify-end">
<button
type="button"
onClick={handleSubmit(onSubmit)}
disabled={isSubmitting || isLoading}
className="btn rounded bg-green-500 font-bold text-white transition-all hover:bg-green-600"
>
{localize('com_ui_save')}
</button>
</div>
</form>
</div>
</OGDialogContent>
</OGDialog>
);
};
export default PeoplePickerAdminSettings;

View file

@ -157,7 +157,7 @@ const AdminSettings = () => {
{localize('com_ui_admin_settings')}
</Button>
</OGDialogTrigger>
<OGDialogContent className="w-1/4 border-border-light bg-surface-primary text-text-primary">
<OGDialogContent className="border-border-light bg-surface-primary text-text-primary md:w-1/4">
<OGDialogTitle>{`${localize('com_ui_admin_settings')} - ${localize(
'com_ui_agents',
)}`}</OGDialogTitle>

View file

@ -146,7 +146,7 @@ const AdminSettings = () => {
{localize('com_ui_admin_settings')}
</Button>
</OGDialogTrigger>
<OGDialogContent className="w-1/4 border-border-light bg-surface-primary text-text-primary">
<OGDialogContent className="border-border-light bg-surface-primary text-text-primary md:w-1/4">
<OGDialogTitle>{`${localize('com_ui_admin_settings')} - ${localize(
'com_ui_memories',
)}`}</OGDialogTitle>

View file

@ -4,6 +4,7 @@ import {
dataService,
promptPermissionsSchema,
memoryPermissionsSchema,
peoplePickerPermissionsSchema,
} from 'librechat-data-provider';
import type {
UseQueryOptions,
@ -132,3 +133,39 @@ export const useUpdateMemoryPermissionsMutation = (
},
);
};
export const useUpdatePeoplePickerPermissionsMutation = (
options?: t.UpdatePeoplePickerPermOptions,
): UseMutationResult<
t.UpdatePermResponse,
t.TError | undefined,
t.UpdatePeoplePickerPermVars,
unknown
> => {
const queryClient = useQueryClient();
const { onMutate, onSuccess, onError } = options ?? {};
return useMutation(
(variables) => {
peoplePickerPermissionsSchema.partial().parse(variables.updates);
return dataService.updatePeoplePickerPermissions(variables);
},
{
onSuccess: (data, variables, context) => {
queryClient.invalidateQueries([QueryKeys.roles, variables.roleName]);
if (onSuccess) {
onSuccess(data, variables, context);
}
},
onError: (...args) => {
const error = args[0];
if (error != null) {
console.error('Failed to update people picker permissions:', error);
}
if (onError) {
onError(...args);
}
},
onMutate,
},
);
};

View file

@ -588,6 +588,10 @@
"com_ui_agents_allow_create": "Allow creating Agents",
"com_ui_agents_allow_share_global": "Allow sharing Agents to all users",
"com_ui_agents_allow_use": "Allow using Agents",
"com_ui_people_picker": "People Picker",
"com_ui_people_picker_allow_view_users": "Allow viewing users",
"com_ui_people_picker_allow_view_groups": "Allow viewing groups",
"com_ui_people_picker_allow_view_roles": "Allow viewing roles",
"com_ui_all": "all",
"com_ui_all_proper": "All",
"com_ui_analyzing": "Analyzing",

View file

@ -275,6 +275,8 @@ export const getRole = (roleName: string) => `${roles()}/${roleName.toLowerCase(
export const updatePromptPermissions = (roleName: string) => `${getRole(roleName)}/prompts`;
export const updateMemoryPermissions = (roleName: string) => `${getRole(roleName)}/memories`;
export const updateAgentPermissions = (roleName: string) => `${getRole(roleName)}/agents`;
export const updatePeoplePickerPermissions = (roleName: string) =>
`${getRole(roleName)}/people-picker`;
/* Conversation Tags */
export const conversationTags = (tag?: string) =>

View file

@ -534,20 +534,9 @@ export const interfaceSchema = z
webSearch: z.boolean().optional(),
peoplePicker: z
.object({
admin: z
.object({
users: z.boolean().optional(),
groups: z.boolean().optional(),
roles: z.boolean().optional(),
})
.optional(),
user: z
.object({
users: z.boolean().optional(),
groups: z.boolean().optional(),
roles: z.boolean().optional(),
})
.optional(),
users: z.boolean().optional(),
groups: z.boolean().optional(),
roles: z.boolean().optional(),
})
.optional(),
marketplace: z
@ -582,16 +571,9 @@ export const interfaceSchema = z
runCode: true,
webSearch: true,
peoplePicker: {
admin: {
users: true,
groups: true,
roles: true,
},
user: {
users: false,
groups: false,
roles: false,
},
users: true,
groups: true,
roles: true,
},
marketplace: {
admin: {

View file

@ -791,6 +791,15 @@ export function updateMemoryPermissions(
return request.put(endpoints.updateMemoryPermissions(variables.roleName), variables.updates);
}
export function updatePeoplePickerPermissions(
variables: m.UpdatePeoplePickerPermVars,
): Promise<m.UpdatePermResponse> {
return request.put(
endpoints.updatePeoplePickerPermissions(variables.roleName),
variables.updates,
);
}
/* Tags */
export function getConversationTags(): Promise<t.TConversationTagsResponse> {
return request.get(endpoints.conversationTags());

View file

@ -273,6 +273,7 @@ export type UpdatePermVars<T> = {
export type UpdatePromptPermVars = UpdatePermVars<p.TPromptPermissions>;
export type UpdateMemoryPermVars = UpdatePermVars<p.TMemoryPermissions>;
export type UpdateAgentPermVars = UpdatePermVars<p.TAgentPermissions>;
export type UpdatePeoplePickerPermVars = UpdatePermVars<p.TPeoplePickerPermissions>;
export type UpdatePermResponse = r.TRole;
@ -297,6 +298,13 @@ export type UpdateAgentPermOptions = MutationOptions<
types.TError | null | undefined
>;
export type UpdatePeoplePickerPermOptions = MutationOptions<
UpdatePermResponse,
UpdatePeoplePickerPermVars,
unknown,
types.TError | null | undefined
>;
export type UpdateConversationTagOptions = MutationOptions<
types.TConversationTag,
types.TConversationTagRequest