React Admin Enterprise Edition - September 2021 Update
The Enterprise Edition of react-admin speeds up the development of complex admin apps by offering 14 additional modules, ranging from editable datagrid to tree structure handling. In the past 6 months, we've added new functionality to existing modules, in reaction to feedback from our customers. We've also added 2 entirely new modules! Here is a summary of the most important changes of the past 6 months.
First, let's introduce the 2 new modules:
- ra-audit-log: Keep track of user actions, and get an overview of the activity of your admin.
- ra-rbac: Manage roles and fine-grained permissions.
Next: check out these exciting new features added to existing modules:
- TypeScript and JavaScript Examples! All packages documentation now have examples in both JavaScript and TypeScript! Check it out on the ra-enterprise site
- ra-preferences: no-code components. Give your users superpowers by allowing them to deeply customize their admin! We also added the ability to synchronize the preferences with your backend.
- ra-tree - Lazy Load Support!
- ra-calendar: Custom side effects for all operations.
- ra-editable-datagrid: Custom side effects for all operations.
- ra-tour: Translation support for the tour steps content.
- ra-navigation: Translation support for breadcrumb items labels.
- ra-realtime: Allows to customize side effects when a list event is received
Now, let's go into the details.
ra-audit-log: Track Changes on Every Resource
ra-audit-log
is a new module that allows tracking every action made by an admin. It's a great way to get an overview of the activity of your users.
It supports two scenarios:
- Your backend doesn't support auditing. In that case,
ra-audit-log
tracks changes client-side and sends them to your API via thedataProvider
. - Your backend already keeps track of the changes. In this case,
ra-audit-log
provides components to display those changes in a user-friendly way.
In the first case, adding auditing features to your admin can be as easy as wrapping your dataProvider with the addEventsForMutations
function:
// in dataProvider.js
import { addEventsForMutations } from '@react-admin/ra-audit-log';
import simpleRestProvider from 'ra-data-simple-rest';
import authProvider from './authProvider';
const dataProvider = addEventsForMutations(
simpleRestProvider('http://path.to.my.api/'),
authProvider,
);
// in authProvider.js
const authProvider = {
// login: () => {},
// logout: () => {},
// You must provide the getIdentity function
getIdentity: () =>
Promise.resolve({
id: '1',
fullName: 'test',
// Avatar is optional
avatar: 'https://myavatar.com',
}),
};
// in App.js
import { Admin, Resource } from 'react-admin';
import dataProvider from './dataProvider';
export const App = () => (
<Admin dataProvider={dataProvider}>
{/* your Resources go here */}
<Resource name="events" />
</Admin>
);
In this case, each mutation (created, update, delete) performed by a user will trigger a call to dataProvider.create('events', [event details])
. This is of course very configurable and you can choose which resources are tracked, and for which mutations.
Whether you opted for client-side tracking or you already have this kind of logs in your backend, ra-audit-log
provides components to display events in a user-friendly way. For instance, the <Timeline>
is a great way to display the changes made by users:
Besides, we even provide a specialized <RecordTimeline>
component that also fetches the events for a given record. This is well suited for asides:
Finally, ra-audit-log
provides the <EventList>
component. A full-featured <List>
for events, pre-configured with a <Datagrid>
and a filter sidebar.
All these components are configurable and you can find their documentation in the ra-audit-log package page.
ra-rbac: Manage Roles and Fine-grained Permissions
When building large admin applications, you often needed to manage roles and fine-grained permissions. ra-rbac
provides a way to do that.
You can define permissions for pages, fields, buttons, etc. Roles and permissions are managed by the authProvider
, which means you can use any data source you want (including an ActiveDirectory server).
The above demo uses the following set of permissions:
const roles = {
accountant: [
{ action: ['list', 'show'], resource: 'products' },
{ action: 'read', resource: 'products.*' },
{ type: 'deny', action: 'read', resource: 'products.description' },
{ action: 'list', resource: 'categories' },
{ action: 'read', resource: 'categories.*' },
{ action: ['list', 'show'], resource: 'customers' },
{ action: 'read', resource: 'customers.*' },
{ action: '*', resource: 'invoices' },
],
contentEditor: [
{
action: ['list', 'create', 'edit', 'delete', 'export'],
resource: 'products',
},
{ action: 'read', resource: 'products.*' },
{ type: 'deny', action: 'read', resource: 'products.stock' },
{ type: 'deny', action: 'read', resource: 'products.sales' },
{ action: 'write', resource: 'products.*' },
{ type: 'deny', action: 'write', resource: 'products.stock' },
{ type: 'deny', action: 'write', resource: 'products.sales' },
{ action: 'list', resource: 'categories' },
{ action: ['list', 'edit'], resource: 'customers' },
{ action: ['list', 'edit'], resource: 'reviews' },
],
stockManager: [
{ action: ['list', 'edit', 'export'], resource: 'products' },
{ action: 'read', resource: 'products.*' },
{
type: 'deny',
action: 'read',
resource: 'products.description',
},
{ action: 'write', resource: 'products.stock' },
{ action: 'write', resource: 'products.sales' },
{ action: 'list', resource: 'categories' },
],
administrator: [{ action: '*', resource: '*' }],
};
There are a few key differences between React-admin and ra-rbac
regarding the permissions:
- Pessimistic Strategy: while React-admin treats permissions in an optimistic way (rendering everything until it fetches the permissions),
ra-rbac
has a pessimistic approach. While permissions are loading,ra-rbac
won't render the components that require permissions, assuming that these components are restricted by default. - Principle Of Least Privilege: A user with no permissions has access to nothing. By default, any restricted action is accessible to nobody. Permissions are additive, each permission granting access to a subset of the application.
- Roles and Permissions:
ra-rbac
lets you define fine-grained permissions for an action on a resource. You can also define groups of permissions, which we call roles, for easier user management.
To facilitate its integration into React-admin, ra-rbac
provides its own version of some components:
<WithPermissions>
: renders its child only if the user has the right permissions (a combination of a resource and an action):
import { WithPermissions } from '@react-admin/ra-rbac';
const RecordToolbar = ({ resource }) => (
<Toolbar>
<WithPermissions action="edit" resource={resource}>
<EditButton />
</WithPermissions>
<WithPermissions action="show" resource={resource}>
<ShowButton />
</WithPermissions>
<WithPermissions action="delete" resource={resource}>
<DeleteButton />
</WithPermissions>
</Toolbar>
);
<Resource>
: automatically restrict access to the views based on the user's permissions.- View components such as
<List>
,<Show>
,<Edit>
and<Create>
: add RBAC control to actions and bulk actions.
import { List } from '@react-admin/ra-rbac';
const authProvider = {
// ...
getPermissions: () =>
Promise.resolve({
permissions: [
{ action: 'list', resource: 'products' },
{ action: 'create', resource: 'products' },
{ action: 'delete', resource: 'products' },
// action 'export' is missing
],
}),
};
export const PostList = props => <List {...props}>...</List>;
// user will see the following actions on top of the list:
// - create
// user will see the following bulk actions upon selection:
// - delete
- UI components such as
<Datagrid>
,<SimpleShowLayout>
,<SimpleForm>
and many more that facilitate permissions-based selection of the fields to display.
import { List, DatagridProps } from '@react-admin/ra-rbac';
import { Datagrid } from '@react-admin/ra-rbac';
const authProvider = {
// ...
getPermissions: () =>
Promise.resolve({
permissions: [
{ action: 'list', resource: 'products' },
{ action: 'read', resource: 'products.thumbnail' },
{ action: 'read', resource: 'products.reference' },
{ action: 'read', resource: 'products.category_id' },
{ action: 'read', resource: 'products.width' },
{ action: 'read', resource: 'products.height' },
{ action: 'read', resource: 'products.price' },
{ action: 'read', resource: 'products.description' },
],
}),
};
const ProductList = (props: DatagridProps) => (
<List {...props}>
{/* ra-rbac Datagrid */}
<Datagrid>
<ImageField source="thumbnail" />
<TextField source="reference" />
<ReferenceField source="category_id" reference="categories">
<TextField source="name" />
</ReferenceField>
<NumberField source="width" />
<NumberField source="height" />
<NumberField source="price" />
<TextField source="description" />
{
// these two columns are not visible to the user
}
<NumberField source="stock" />
<NumberField source="sales" />
</Datagrid>
</List>
);
ra-rbac
also provide hooks and functions to ease restrictions based on roles and permissions inside your components:
useCanAccess
: fetch the role definitions, then checks whether the requested action and resource are allowed for the current user.
import { useCanAccess } from '@react-admin/ra-rbac';
const DeleteUserButton = ({ record }) => {
const { loading, canAccess } = useCanAccess({
action: 'delete',
resource: 'users',
record,
});
if (loading || !canAccess) return null;
return <DeleteButton record={record} resource="users" />;
};
usePermissions
: a replacement for react-admin'susePermissions
that returns an array of permissions, resulting in the merge of the user permissions and the permissions from the user roles.
const authProvider = {
// ...
getPermissions: () =>
Promise.resolve({
permissions: [
{
action: ['read', 'write'],
resource: 'users',
record: { id: '123' },
},
],
roles: ['reader'],
}),
getRoles: () =>
Promise.resolve({
admin: [{ action: '*', resource: '*' }],
reader: [{ action: 'read', resource: '*' }],
}),
};
const { loading, permissions } = usePermissions();
// {
// loading: false,
// permissions: [
// { action: "read", resource: "*" },
// { action: ["read", "write"], resource: "users", record: { "id": "123" } },
// ],
// };
canAccess
: a helper function that checks if the current permissions allow the user to execute an action on a resource (and optionally a record).
import { List, Datagrid, TextField } from 'react-admin';
import { canAccess } from '@react-admin/ra-rbac';
const authProvider = {
checkAuth: () => Promise.resolve(),
login: () => Promise.resolve(),
logout: () => Promise.resolve(),
checkError: () => Promise.resolve(),
getPermissions: () =>
Promise.resolve({
permissions: [
{ action: 'list', resource: 'products' },
{ action: 'read', resource: 'products.price' },
],
}),
};
const ProductList = props => {
const { loading, permissions } = usePermissions();
if (loading) return null;
return (
<List {...props}>
<Datagrid>
<TextField source="id" />
<TextField source="reference" />
<TextField source="width" />
<TextField source="height" />
{canAccess({
permissions,
action: 'read',
resource: 'products.price',
}) && <TextField source="price" />}
{/* this column will not render */}
{canAccess({
permissions,
action: 'read',
resource: 'products.stock',
}) && <TextField source="stock" />}
</Datagrid>
</List>
);
};
TypeScript and JavaScript Examples
Some of our customers use TypeScript, and some don't. For the latter category, we've updated the Enterprise modules documentation to display code examples both in TypeScript and in JavaScript. Check it out on the ra-enterprise site. Your language of choice is persisted locally, too!
.
ra-preferences: no-code Components
It can be hard to predict what your users really need on an application. Besides, their needs evolve over time. We believe that providing end-users a way to customize the application can be a game-changer, as it removes the need for developer time and accelerates the time to market.
This is why ra-preferences
now provides our first iteration of components that users can configure through the UI:
<Datagrid>
lets end users show/hide columns, and even add computed columns<List>
lets end users apply default filters and change the view title<SimpleForm>
lets end users adjust the input style and density, and the redirection after save<SimpleList>
lets end users change the text displayed on each line, using a simple templating engine
In addition to those components, ra-preferences
also provides the building blocks for you to create your own configurable components.
Conclusion
If you're an Enterprise Edition customer, you get all these updates for free! Just run yarn upgrade
to get the latest version of the @react-admin/XXX
modules.
If you're not an Enterprise Edition customer yet, check out our plans: from 125€/month, you can dramatically speed up your development by using these modules that we've carefully crafted, tested, and documented, instead of writing your own code. A single one of these modules would already provide enough value to justify the subscription - but by subscribing, you get access to all 12 of them. And unlimited professional support!