Hi,
I'm having trouble working out how to add new settings to the react UI, the AI Agent isn't working as it keeps going into an endless searching mode which just sits there and is burning credits like crazy. I've created new settings for SendGrid emailing, from the information I found that the admin console is supposed to automatically identify the settings and render them, but I couldn't get this to work.
Here's what I have so far:
public static class SendGridSettings { private const string Prefix = "SendGrid";
public const string Enabled = Prefix + ".Enabled";
public const string FromName = Prefix + ".FromName";
public const string FromAddress = Prefix + ".FromAddress";
public const string ApiKey = Prefix + ".ApiKey";
}
public class SendGridSettingDefinitionProvider : SettingDefinitionProvider { public override void Define(ISettingDefinitionContext context) { context.Add( new SettingDefinition(SendGridSettings.Enabled, defaultValue: false.ToString(), isVisibleToClients: true), new SettingDefinition(SendGridSettings.FromName, defaultValue: "<my email sender name>", isVisibleToClients: true), new SettingDefinition(SendGridSettings.FromAddress, defaultValue: "<my email sender>", isVisibleToClients: true), new SettingDefinition(SendGridSettings.ApiKey, isEncrypted: true, isVisibleToClients: true) ); } }
public static class SendGridPermissions { public const string GroupName = "SettingManagement";
public const string SendGrid = GroupName + ".SendGrid";
}
public class SendGridPermissionDefinitionProvider : PermissionDefinitionProvider { public override void Define(IPermissionDefinitionContext context) { var settingManagementGroup = context.GetGroupOrNull("SettingManagement"); if (settingManagementGroup == null) { settingManagementGroup = context.AddGroup("SettingManagement", L("Permission:SettingManagement")); }
settingManagementGroup.AddPermission(SendGridPermissions.SendGrid, L("Permission:SendGrid"));
}
private static LocalizableString L(string name)
{
return LocalizableString.Create<MyResource>(name);
}
}
This didn't create a new tab in the admin console so i tried creating a pSendGridSettings.tsx in react/src/pages/settings but I couldn't get this to work with the correct imports as I couldn't find any documentation on how to do this.
What is the best way to add new settings to the admin console?
Regards Raff
2 Answer(s)
-
0
In ABP React UI, the Setting Management page does not automatically create a new tab/group just because you added
SettingDefinitions. Defining settings only makes them available to the backend (and to the client ifisVisibleToClients: true), but the admin UI tabs are contributed explicitly by the UI package/module.The recommended/standard way to add a new “tab” (group) in the Administration → Settings (Setting Management) UI is:
- Define your settings (you already did with
SendGridSettingDefinitionProvider). - Create an application service that reads/writes those settings (typically using
ISettingProvider+ISettingManager). - Add a custom UI group to the Setting Management page:
- For MVC/Blazor/Angular there are documented “custom setting group” contribution mechanisms in the Setting Management module docs.
- For React, there isn’t an equivalent “auto-discover my setting definitions and render them as a tab” mechanism in the same way; you typically create your own page/component and wire it into the admin menu, or extend the setting-management UI in the way that UI package exposes (if it does for your version). If you were trying to drop a
pSendGridSettings.tsxundersrc/pages/settings, that won’t work unless your React app’s routing/menu is configured to load it.
- Define your settings (you already did with
Backend part (what you should keep / adjust):
- Keep
isVisibleToClients: truefor non-secret values. - Keep
isEncrypted: trueforApiKey(good). - Don’t expect this to create an admin tab by itself; it only makes the setting available.
- Keep
Practical approach for React (works reliably):
- Create an app service like:
public class SendGridSettingsAppService : ApplicationService { private readonly ISettingProvider _settingProvider; private readonly ISettingManager _settingManager; public SendGridSettingsAppService(ISettingProvider settingProvider, ISettingManager settingManager) { _settingProvider = settingProvider; _settingManager = settingManager; } public async Task<SendGridSettingsDto> GetAsync() { return new SendGridSettingsDto { Enabled = await _settingProvider.GetAsync<bool>(SendGridSettings.Enabled), FromName = await _settingProvider.GetOrNullAsync(SendGridSettings.FromName), FromAddress = await _settingProvider.GetOrNullAsync(SendGridSettings.FromAddress), ApiKey = await _settingProvider.GetOrNullAsync(SendGridSettings.ApiKey) }; } public async Task UpdateAsync(SendGridSettingsDto input) { await _settingManager.SetGlobalAsync(SendGridSettings.Enabled, input.Enabled.ToString()); await _settingManager.SetGlobalAsync(SendGridSettings.FromName, input.FromName); await _settingManager.SetGlobalAsync(SendGridSettings.FromAddress, input.FromAddress); await _settingManager.SetGlobalAsync(SendGridSettings.ApiKey, input.ApiKey); } }(Then generate the React service proxies and build a custom admin page that calls `GetAsync/UpdateAsync`.)- Add a new admin menu item in React to navigate to your “SendGrid Settings” page (instead of trying to make it appear automatically under the Setting Management tabs).
- If you specifically want it inside the built-in Setting Management UI: follow the Setting Management module’s “custom setting group” approach shown in the docs (note: the docs show this clearly for MVC/Blazor/Angular; React doesn’t have the same “drop-in contributor” story documented there).
Sources (validated):
- https://abp.io/docs/10.4/modules/setting-management
- https://abp.io/community/articles/mb2w01fe
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Hi,
There's no auto-discover mechanism that turns
SettingDefinitions into admin UI tabs.SettingDefinitionProvideronly registers the setting on the backend (and exposes it to clients whenisVisibleToClients: true); it doesn't render anything by itself.On the React UI specifically, the Admin Console at
/admin-consoleis shipped as a prebuilt SPA inside theVolo.Abp.AdminConsoleNuGet package and is meant to stay in sync with ABP updates. Its tabs (Account / Emailing / Feature management / Identity management / SaaS management) are fixed; there's no contributor API to inject a custom tab. See the docs:- https://abp.io/docs/latest/framework/ui/react/admin-console
- https://abp.io/docs/latest/framework/ui/react/customization
The recommended approach is to keep your
SettingDefinitionProvider/PermissionDefinitionProviderexactly as you have them, add a small AppService to read/write the values, and build the page in the main React app (thereact/folder in your solution — that's the code you own and can freely customize).Step 1 — Add an AppService in
*.Application(and a DTO in*.Application.Contracts):// *.Application.Contracts/SendGrid/SendGridSettingsDto.cs public class SendGridSettingsDto { public bool Enabled { get; set; } public string? FromName { get; set; } public string? FromAddress { get; set; } public string? ApiKey { get; set; } } // *.Application.Contracts/SendGrid/ISendGridSettingsAppService.cs public interface ISendGridSettingsAppService : IApplicationService { Task<SendGridSettingsDto> GetAsync(); Task UpdateAsync(SendGridSettingsDto input); } // *.Application/SendGrid/SendGridSettingsAppService.cs [Authorize(SendGridPermissions.SendGrid)] public class SendGridSettingsAppService : YourProjectNameAppService, ISendGridSettingsAppService { private readonly ISettingManager _settingManager; public SendGridSettingsAppService(ISettingManager settingManager) { _settingManager = settingManager; } public async Task<SendGridSettingsDto> GetAsync() { return new SendGridSettingsDto { Enabled = bool.Parse(await SettingProvider.GetOrNullAsync(SendGridSettings.Enabled) ?? "false"), FromName = await SettingProvider.GetOrNullAsync(SendGridSettings.FromName), FromAddress = await SettingProvider.GetOrNullAsync(SendGridSettings.FromAddress), ApiKey = await SettingProvider.GetOrNullAsync(SendGridSettings.ApiKey), }; } public async Task UpdateAsync(SendGridSettingsDto input) { await _settingManager.SetGlobalAsync(SendGridSettings.Enabled, input.Enabled.ToString()); await _settingManager.SetGlobalAsync(SendGridSettings.FromName, input.FromName ?? string.Empty); await _settingManager.SetGlobalAsync(SendGridSettings.FromAddress, input.FromAddress ?? string.Empty); await _settingManager.SetGlobalAsync(SendGridSettings.ApiKey, input.ApiKey ?? string.Empty); } }ABP's conventional controllers will expose this as
GET/PUT /api/app/send-grid-settingsautomatically — no extra routing needed.isEncrypted: trueon theApiKeysetting is handled transparently byISettingManager, so you get plain text in the DTO and an encrypted value in the database.Step 2 — Add an API client in
react/src/lib/api/sendgrid.ts:import { api } from './axios' export interface SendGridSettingsDto { enabled: boolean fromName: string | null fromAddress: string | null apiKey: string | null } export const sendGridApi = { get: () => api.get<SendGridSettingsDto>('/app/send-grid-settings').then((r) => r.data), update: (input: SendGridSettingsDto) => api.put('/app/send-grid-settings', input), }Note the path is
/app/send-grid-settings, not/api/app/send-grid-settings— the axiosbaseURLalready includes/api.Step 3 — Add the page in
react/src/pages/sendgrid/SendGridSettingsPage.tsx:import { useEffect, useState } from 'react' import { toast } from 'sonner' import { Button } from '@/components/ui/button' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Checkbox } from '@/components/ui/checkbox' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { sendGridApi } from '@/lib/api/sendgrid' export function SendGridSettingsPage() { const [form, setForm] = useState({ enabled: false, fromName: '', fromAddress: '', apiKey: '' }) const [loading, setLoading] = useState(true) const [saving, setSaving] = useState(false) useEffect(() => { sendGridApi.get() .then((d) => setForm({ enabled: d.enabled, fromName: d.fromName ?? '', fromAddress: d.fromAddress ?? '', apiKey: d.apiKey ?? '', })) .catch(() => toast.error('Failed to load SendGrid settings')) .finally(() => setLoading(false)) }, []) async function onSave() { setSaving(true) try { await sendGridApi.update(form) toast.success('SendGrid settings saved') } catch { toast.error('Failed to save SendGrid settings') } finally { setSaving(false) } } return ( <div className="space-y-6"> <h1 className="text-3xl font-bold tracking-tight">SendGrid Settings</h1> <Card> <CardHeader><CardTitle>Configuration</CardTitle></CardHeader> <CardContent className="space-y-4"> <div className="flex items-center gap-2"> <Checkbox id="enabled" checked={form.enabled} disabled={loading} onCheckedChange={(v) => setForm({ ...form, enabled: v === true })} /> <Label htmlFor="enabled">Enabled</Label> </div> <div className="space-y-2"> <Label htmlFor="fromName">From Name</Label> <Input id="fromName" value={form.fromName} disabled={loading} onChange={(e) => setForm({ ...form, fromName: e.target.value })} /> </div> <div className="space-y-2"> <Label htmlFor="fromAddress">From Address</Label> <Input id="fromAddress" type="email" value={form.fromAddress} disabled={loading} onChange={(e) => setForm({ ...form, fromAddress: e.target.value })} /> </div> <div className="space-y-2"> <Label htmlFor="apiKey">API Key</Label> <Input id="apiKey" type="password" value={form.apiKey} disabled={loading} onChange={(e) => setForm({ ...form, apiKey: e.target.value })} /> </div> <Button onClick={onSave} disabled={loading || saving}> {saving ? 'Saving...' : 'Save'} </Button> </CardContent> </Card> </div> ) }Step 4 — Register the route in
react/src/routes/router.tsx:import { SendGridSettingsPage } from '@/pages/sendgrid/SendGridSettingsPage' const sendGridSettingsRoute = createRoute({ getParentRoute: () => rootRoute, path: '/sendgrid-settings', component: SendGridSettingsPage, beforeLoad: createPermissionGuard('SettingManagement.SendGrid'), }) const routeTree = rootRoute.addChildren([ indexRoute, forbiddenRoute, accountRoute, identityRoute, sendGridSettingsRoute, ])Step 5 — Add a sidebar menu item in
react/src/lib/routing/route-config.ts:import { Mail } from 'lucide-react' // inside routeConfig: { path: '/sendgrid-settings', nameKey: 'Menu:SendGridSettings', icon: Mail, order: 7, requiredPolicy: 'SettingManagement.SendGrid', },Then add
"Menu:SendGridSettings": "SendGrid Settings"toreact/src/locales/en.json(and the other locale files you support).That's it — the user will see a SendGrid Settings entry in the sidebar (gated by your
SettingManagement.SendGridpermission), the page calls your AppService, andISettingManagerhandles persistence including the encrypted API key.Here's how it looks in a fresh 10.4.0 React tiered template I built to confirm the flow end to end:
Thanks
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
