Skip to content

Commit 4640e1b

Browse files
authored
🛡️ feat: Add Role Dropdown to Prompt/Agents Admin Settings (#4922)
* style: update AdminSettings dialog content styles for improved accessibility/theming * style: update icon colors in ExportAndShareMenu for improved theming * feat: enhance DropdownPopup component with additional props for customization * feat: add role selection dropdown to AdminSettings for enhanced user permissions management * feat: add role selection dropdown to AdminSettings for Prompt permission management * style: add gap to button in AdminSettings for improved layout * feat: add warning message for Admin role access in Permissions settings
1 parent 1c05251 commit 4640e1b

File tree

5 files changed

+227
-95
lines changed

5 files changed

+227
-95
lines changed

client/src/components/Chat/ExportAndShareMenu.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,12 @@ export default function ExportAndShareMenu({
5050
{
5151
label: localize('com_endpoint_export'),
5252
onClick: exportHandler,
53-
icon: <Upload className="icon-md mr-2 dark:text-gray-300" />,
53+
icon: <Upload className="icon-md mr-2 text-text-secondary" />,
5454
},
5555
{
5656
label: localize('com_ui_share'),
5757
onClick: shareHandler,
58-
icon: <Share2 className="icon-md mr-2 dark:text-gray-300" />,
58+
icon: <Share2 className="icon-md mr-2 text-text-secondary" />,
5959
show: isSharedButtonEnabled,
6060
},
6161
];
@@ -72,7 +72,7 @@ export default function ExportAndShareMenu({
7272
aria-label="Export options"
7373
className="inline-flex size-10 items-center justify-center rounded-lg border border-border-light bg-transparent text-text-primary transition-all ease-in-out hover:bg-surface-tertiary disabled:pointer-events-none disabled:opacity-50 radix-state-open:bg-surface-tertiary"
7474
>
75-
<Upload className="icon-md dark:text-gray-300" aria-hidden="true" focusable="false" />
75+
<Upload className="icon-md text-text-secondary" aria-hidden="true" focusable="false" />
7676
</Ariakit.MenuButton>
7777
}
7878
items={dropdownItems}

client/src/components/Prompts/AdminSettings.tsx

Lines changed: 102 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
import { useMemo, useEffect } from 'react';
1+
import * as Ariakit from '@ariakit/react';
2+
import { useMemo, useEffect, useState } from 'react';
23
import { ShieldEllipsis } from 'lucide-react';
34
import { useForm, Controller } from 'react-hook-form';
45
import { Permissions, SystemRoles, roleDefaults, PermissionTypes } from 'librechat-data-provider';
56
import type { Control, UseFormSetValue, UseFormGetValues } from 'react-hook-form';
67
import { OGDialog, OGDialogTitle, OGDialogContent, OGDialogTrigger } from '~/components/ui';
78
import { useUpdatePromptPermissionsMutation } from '~/data-provider';
89
import { useLocalize, useAuthContext } from '~/hooks';
9-
import { Button, Switch } from '~/components/ui';
10+
import { Button, Switch, DropdownPopup } from '~/components/ui';
1011
import { useToastContext } from '~/Providers';
1112

1213
type FormValues = Record<Permissions, boolean>;
@@ -19,8 +20,6 @@ type LabelControllerProps = {
1920
getValues: UseFormGetValues<FormValues>;
2021
};
2122

22-
const defaultValues = roleDefaults[SystemRoles.USER];
23-
2423
const LabelController: React.FC<LabelControllerProps> = ({
2524
control,
2625
promptPerm,
@@ -32,7 +31,6 @@ const LabelController: React.FC<LabelControllerProps> = ({
3231
<button
3332
className="cursor-pointer select-none"
3433
type="button"
35-
// htmlFor={promptPerm}
3634
onClick={() =>
3735
setValue(promptPerm, !getValues(promptPerm), {
3836
shouldDirty: true,
@@ -70,6 +68,16 @@ const AdminSettings = () => {
7068
},
7169
});
7270

71+
const [isRoleMenuOpen, setIsRoleMenuOpen] = useState(false);
72+
const [selectedRole, setSelectedRole] = useState<SystemRoles>(SystemRoles.USER);
73+
74+
const defaultValues = useMemo(() => {
75+
if (roles?.[selectedRole]) {
76+
return roles[selectedRole][PermissionTypes.PROMPTS];
77+
}
78+
return roleDefaults[selectedRole][PermissionTypes.PROMPTS];
79+
}, [roles, selectedRole]);
80+
7381
const {
7482
reset,
7583
control,
@@ -79,20 +87,16 @@ const AdminSettings = () => {
7987
formState: { isSubmitting },
8088
} = useForm<FormValues>({
8189
mode: 'onChange',
82-
defaultValues: useMemo(() => {
83-
if (roles?.[SystemRoles.USER]) {
84-
return roles[SystemRoles.USER][PermissionTypes.PROMPTS];
85-
}
86-
87-
return defaultValues[PermissionTypes.PROMPTS];
88-
}, [roles]),
90+
defaultValues,
8991
});
9092

9193
useEffect(() => {
92-
if (roles?.[SystemRoles.USER]?.[PermissionTypes.PROMPTS]) {
93-
reset(roles[SystemRoles.USER][PermissionTypes.PROMPTS]);
94+
if (roles?.[selectedRole]?.[PermissionTypes.PROMPTS]) {
95+
reset(roles[selectedRole][PermissionTypes.PROMPTS]);
96+
} else {
97+
reset(roleDefaults[selectedRole][PermissionTypes.PROMPTS]);
9498
}
95-
}, [roles, reset]);
99+
}, [roles, selectedRole, reset]);
96100

97101
if (user?.role !== SystemRoles.ADMIN) {
98102
return null;
@@ -103,20 +107,35 @@ const AdminSettings = () => {
103107
promptPerm: Permissions.SHARED_GLOBAL,
104108
label: localize('com_ui_prompts_allow_share_global'),
105109
},
106-
{
107-
promptPerm: Permissions.USE,
108-
label: localize('com_ui_prompts_allow_use'),
109-
},
110110
{
111111
promptPerm: Permissions.CREATE,
112112
label: localize('com_ui_prompts_allow_create'),
113113
},
114+
{
115+
promptPerm: Permissions.USE,
116+
label: localize('com_ui_prompts_allow_use'),
117+
},
114118
];
115119

116120
const onSubmit = (data: FormValues) => {
117-
mutate({ roleName: SystemRoles.USER, updates: data });
121+
mutate({ roleName: selectedRole, updates: data });
118122
};
119123

124+
const roleDropdownItems = [
125+
{
126+
label: SystemRoles.USER,
127+
onClick: () => {
128+
setSelectedRole(SystemRoles.USER);
129+
},
130+
},
131+
{
132+
label: SystemRoles.ADMIN,
133+
onClick: () => {
134+
setSelectedRole(SystemRoles.ADMIN);
135+
},
136+
},
137+
];
138+
120139
return (
121140
<OGDialog>
122141
<OGDialogTrigger asChild>
@@ -129,33 +148,70 @@ const AdminSettings = () => {
129148
<span className="hidden sm:flex">{localize('com_ui_admin')}</span>
130149
</Button>
131150
</OGDialogTrigger>
132-
<OGDialogContent className="bg-white dark:border-gray-700 dark:bg-gray-850 dark:text-gray-300">
133-
<OGDialogTitle>{`${localize('com_ui_admin_settings')} - ${localize(
134-
'com_ui_prompts',
135-
)}`}</OGDialogTitle>
136-
<form className="p-2" onSubmit={handleSubmit(onSubmit)}>
137-
<div className="py-5">
138-
{labelControllerData.map(({ promptPerm, label }) => (
139-
<LabelController
140-
key={promptPerm}
141-
control={control}
142-
promptPerm={promptPerm}
143-
label={label}
144-
getValues={getValues}
145-
setValue={setValue}
146-
/>
147-
))}
148-
</div>
149-
<div className="flex justify-end">
150-
<button
151-
type="submit"
152-
disabled={isSubmitting || isLoading}
153-
className="btn rounded bg-green-500 font-bold text-white transition-all hover:bg-green-600"
154-
>
155-
{localize('com_ui_save')}
156-
</button>
151+
<OGDialogContent className="w-1/4 border-border-light bg-surface-primary text-text-primary">
152+
<OGDialogTitle>
153+
{`${localize('com_ui_admin_settings')} - ${localize('com_ui_prompts')}`}
154+
</OGDialogTitle>
155+
<div className="p-2">
156+
{/* Role selection dropdown */}
157+
<div className="flex items-center gap-2">
158+
<span className="font-medium">{localize('com_ui_role_select')}:</span>
159+
<DropdownPopup
160+
menuId="prompt-role-dropdown"
161+
isOpen={isRoleMenuOpen}
162+
setIsOpen={setIsRoleMenuOpen}
163+
trigger={
164+
<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">
165+
{selectedRole}
166+
</Ariakit.MenuButton>
167+
}
168+
items={roleDropdownItems}
169+
className="border border-border-light bg-surface-primary"
170+
itemClassName="hover:bg-surface-tertiary items-center justify-center"
171+
sameWidth={true}
172+
/>
157173
</div>
158-
</form>
174+
<form onSubmit={handleSubmit(onSubmit)}>
175+
<div className="py-5">
176+
{labelControllerData.map(({ promptPerm, label }) => (
177+
<div key={promptPerm}>
178+
<LabelController
179+
control={control}
180+
promptPerm={promptPerm}
181+
label={label}
182+
getValues={getValues}
183+
setValue={setValue}
184+
/>
185+
{selectedRole === SystemRoles.ADMIN && promptPerm === Permissions.USE && (
186+
<>
187+
<div className="mb-2 max-w-full whitespace-normal break-words text-sm text-red-600">
188+
<span>{localize('com_ui_admin_access_warning')}</span>
189+
{'\n'}
190+
<a
191+
href="https://www.librechat.ai/docs/configuration/librechat_yaml/object_structure/interface"
192+
target="_blank"
193+
rel="noreferrer"
194+
className="text-blue-500 underline"
195+
>
196+
{localize('com_ui_more_info')}
197+
</a>
198+
</div>
199+
</>
200+
)}
201+
</div>
202+
))}
203+
</div>
204+
<div className="flex justify-end">
205+
<button
206+
type="submit"
207+
disabled={isSubmitting || isLoading}
208+
className="btn rounded bg-green-500 font-bold text-white transition-all hover:bg-green-600"
209+
>
210+
{localize('com_ui_save')}
211+
</button>
212+
</div>
213+
</form>
214+
</div>
159215
</OGDialogContent>
160216
</OGDialog>
161217
);

0 commit comments

Comments
 (0)