Add permissions to plugins links in the menu only

Signed-off-by: soupette <cyril.lpz@gmail.com>
This commit is contained in:
soupette 2020-06-09 12:05:59 +02:00 committed by Alexandre Bodin
parent d282c7d5d6
commit b68245b8b1
23 changed files with 445 additions and 181 deletions

View File

@ -22,34 +22,29 @@ const LinkLabel = styled.span`
padding-left: 2.5rem;
`;
const LeftMenuLinkContent = ({
destination,
iconName,
label,
location,
source,
suffixUrlToReplaceForLeftMenuHighlight,
}) => {
// TODO: refacto this file
const LeftMenuLinkContent = ({ destination, iconName, label, location }) => {
const isLinkActive = startsWith(
location.pathname.replace('/admin', '').concat('/'),
destination.replace(suffixUrlToReplaceForLeftMenuHighlight, '').concat('/')
destination.concat('/')
);
// Check if messageId exists in en locale to prevent warning messages
const content = en[label] ? (
<FormattedMessage
id={label}
defaultMessage="{label}"
values={{
label: `${label}`,
}}
>
{message => <LinkLabel>{message}</LinkLabel>}
</FormattedMessage>
) : (
<LinkLabel>{label}</LinkLabel>
);
const labelId = label.id || label;
const content =
en[labelId] || label.defaultMessage ? (
<FormattedMessage
id={labelId}
defaultMessage={label.defaultMessage || '{label}'}
values={{
label: `${label.id || label}`,
}}
>
{message => <LinkLabel>{message}</LinkLabel>}
</FormattedMessage>
) : (
<LinkLabel>{labelId}</LinkLabel>
);
// Create external or internal link.
return destination.includes('http') ? (
@ -68,7 +63,6 @@ const LeftMenuLinkContent = ({
className={isLinkActive ? 'linkActive' : ''}
to={{
pathname: destination,
search: source ? `?source=${source}` : '',
}}
>
<LeftMenuIcon icon={iconName} />
@ -80,17 +74,10 @@ const LeftMenuLinkContent = ({
LeftMenuLinkContent.propTypes = {
destination: PropTypes.string.isRequired,
iconName: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
label: PropTypes.oneOfType([PropTypes.object, PropTypes.string]).isRequired,
location: PropTypes.shape({
pathname: PropTypes.string,
}).isRequired,
source: PropTypes.string,
suffixUrlToReplaceForLeftMenuHighlight: PropTypes.string,
};
LeftMenuLinkContent.defaultProps = {
source: '',
suffixUrlToReplaceForLeftMenuHighlight: '',
};
export default withRouter(LeftMenuLinkContent);

View File

@ -1,33 +0,0 @@
import styled from 'styled-components';
const Plugin = styled.div`
cursor: pointer;
position: absolute;
top: 10px;
left: calc(100% - 4px);
display: inline-block;
width: auto;
height: 20px;
transition: right 1s ease-in-out;
span {
display: inline-block;
overflow: hidden;
width: auto;
height: 20px;
padding: 0 14px 0 10px;
color: #ffffff;
font-size: 12px;
line-height: 20px;
background: #0097f7;
border-radius: 3px;
transition: transform 0.3s ease-in-out;
white-space: pre;
&:hover {
transform: translateX(calc(-100% + 9px));
}
}
`;
export default Plugin;

View File

@ -5,29 +5,11 @@
*/
import React from 'react';
import { upperFirst } from 'lodash';
import PropTypes from 'prop-types';
import LeftMenuLinkContent from './LeftMenuLinkContent';
import Plugin from './Plugin';
const LeftMenuLink = ({
destination,
iconName,
label,
location,
source,
suffixUrlToReplaceForLeftMenuHighlight,
}) => {
const plugin =
source !== 'content-manager' && source !== '' ? (
<Plugin>
<span>{upperFirst(source.split('-').join(' '))}</span>
</Plugin>
) : (
''
);
const LeftMenuLink = ({ destination, iconName, label, location }) => {
return (
<>
<LeftMenuLinkContent
@ -35,10 +17,7 @@ const LeftMenuLink = ({
iconName={iconName}
label={label}
location={location}
source={source}
suffixUrlToReplaceForLeftMenuHighlight={suffixUrlToReplaceForLeftMenuHighlight}
/>
{plugin}
</>
);
};
@ -46,18 +25,14 @@ const LeftMenuLink = ({
LeftMenuLink.propTypes = {
destination: PropTypes.string.isRequired,
iconName: PropTypes.string,
label: PropTypes.string.isRequired,
label: PropTypes.oneOfType([PropTypes.object, PropTypes.string]).isRequired,
location: PropTypes.shape({
pathname: PropTypes.string,
}).isRequired,
source: PropTypes.string,
suffixUrlToReplaceForLeftMenuHighlight: PropTypes.string,
};
LeftMenuLink.defaultProps = {
iconName: 'circle',
source: '',
suffixUrlToReplaceForLeftMenuHighlight: '',
};
export default LeftMenuLink;

View File

@ -4,8 +4,6 @@ import { useLocation } from 'react-router-dom';
import PropTypes from 'prop-types';
import { get, snakeCase, isEmpty } from 'lodash';
import messages from './messages.json';
import LeftMenuLinkSection from '../LeftMenuLinkSection';
const LeftMenuLinkContainer = ({ plugins }) => {
@ -34,29 +32,8 @@ const LeftMenuLinkContainer = ({ plugins }) => {
return acc;
}, {});
// Generate the list of plugin links (plugins without a mainComponent should not appear in the left menu)
const pluginsLinks = Object.values(plugins)
.filter(
plugin => plugin.id !== 'email' && plugin.id !== 'content-manager' && !!plugin.mainComponent
)
.map(plugin => {
const pluginSuffixUrl = plugin.suffixUrl ? plugin.suffixUrl(plugins) : '';
return {
icon: get(plugin, 'icon') || 'plug',
label: get(plugin, 'name'),
destination: `/plugins/${get(plugin, 'id')}${pluginSuffixUrl}`,
};
});
const menu = {
...contentTypesSections,
plugins: {
searchable: false,
name: 'plugins',
emptyLinksListMessage: messages.noPluginsInstalled.id,
links: pluginsLinks,
},
};
return Object.keys(menu).map(current => (

View File

@ -214,7 +214,7 @@ export class Admin extends React.Component {
exact
/>
<Route key="7" path="" component={NotFoundPage} />
<Route key="8" path="404" component={NotFoundPage} />
<Route key="8" path="/404" component={NotFoundPage} />
</Switch>
</Content>
</div>

View File

@ -4,7 +4,7 @@
*
*/
import React, { useContext, useEffect, useReducer } from 'react';
import React, { useContext, useEffect, useMemo, useReducer } from 'react';
import PropTypes from 'prop-types';
import { useLocation } from 'react-router-dom';
import { UserContext, hasPermissions } from 'strapi-helper-plugin';
@ -15,6 +15,7 @@ import {
LeftMenuLinkContainer,
LinksContainer,
} from '../../components/LeftMenu';
import init from './init';
import reducer, { initialState } from './reducer';
import Loader from './Loader';
import Wrapper from './Wrapper';
@ -22,8 +23,21 @@ import Wrapper from './Wrapper';
const LeftMenu = ({ version, plugins }) => {
const location = useLocation();
const permissions = useContext(UserContext);
const [{ generalSectionLinks, isLoading }, dispatch] = useReducer(reducer, initialState);
const filteredLinks = generalSectionLinks.filter(link => link.isDisplayed);
const [{ generalSectionLinks, isLoading, pluginsSectionLinks }, dispatch] = useReducer(
reducer,
initialState,
() => init(initialState, plugins)
);
const generalSectionLinksFiltered = useMemo(
() => generalSectionLinks.filter(link => link.isDisplayed),
[generalSectionLinks]
);
const pluginsSectionLinksFiltered = useMemo(
() => pluginsSectionLinks.filter(link => link.isDisplayed),
[pluginsSectionLinks]
);
console.log(pluginsSectionLinks);
useEffect(() => {
const getLinksPermissions = async () => {
@ -33,15 +47,21 @@ const LeftMenu = ({ version, plugins }) => {
return { index, hasPermission };
};
const generalSectionLinksArrayOfPromises = generalSectionLinks.map((_, index) =>
checkPermissions(index, generalSectionLinks[index].permissions)
);
const generateArrayOfPromises = array =>
array.map((_, index) => checkPermissions(index, array[index].permissions));
const results = await Promise.all(generalSectionLinksArrayOfPromises);
const generalSectionLinksArrayOfPromises = generateArrayOfPromises(generalSectionLinks);
const pluginsSectionLinksArrayOfPromises = generateArrayOfPromises(pluginsSectionLinks);
const generalSectionResults = await Promise.all(generalSectionLinksArrayOfPromises);
const pluginsSectionResults = await Promise.all(pluginsSectionLinksArrayOfPromises);
dispatch({
type: 'SET_LINK_PERMISSIONS',
results,
data: {
generalSectionLinks: generalSectionResults,
pluginsSectionLinks: pluginsSectionResults,
},
});
dispatch({
@ -59,11 +79,19 @@ const LeftMenu = ({ version, plugins }) => {
<LeftMenuHeader />
<LinksContainer>
<LeftMenuLinkContainer plugins={plugins} />
{filteredLinks.length && (
<LeftMenuLinksSection
section="plugins"
name="plugins"
links={pluginsSectionLinksFiltered}
location={location}
searchable={false}
emptyLinksListMessage="app.components.LeftMenuLinkContainer.noPluginsInstalled"
/>
{generalSectionLinksFiltered.length && (
<LeftMenuLinksSection
section="general"
name="general"
links={filteredLinks}
links={generalSectionLinksFiltered}
location={location}
searchable={false}
/>

View File

@ -0,0 +1,23 @@
import { get, omit, set, sortBy } from 'lodash';
const sortLinks = links => sortBy(links, object => object.name);
const init = (initialState, plugins = {}) => {
const pluginsLinks = Object.values(plugins).reduce((acc, current) => {
const pluginsSectionLinks = get(current, 'menu.pluginsSectionLinks', []);
return [...acc, ...pluginsSectionLinks];
}, []);
const sortedLinks = sortLinks(pluginsLinks).map(link => {
return { ...omit(link, 'name'), isDisplayed: false };
});
if (sortedLinks.length) {
set(initialState, 'pluginsSectionLinks', sortedLinks);
}
return initialState;
};
export default init;
export { sortLinks };

View File

@ -46,11 +46,14 @@ const initialState = {
{ action: 'admin::roles.update', subject: null },
{ action: 'admin::roles.read', subject: null },
{ action: 'admin::roles.delete', subject: null },
// TODO this should be set by the plugin directly
// media library
{ action: 'plugins::upload.settings.read', subject: null },
],
},
],
pluginsSectionLinks: [],
isLoading: true,
};
@ -58,12 +61,12 @@ const reducer = (state, action) =>
produce(state, draftState => {
switch (action.type) {
case 'SET_LINK_PERMISSIONS': {
action.results.forEach(result => {
set(
draftState,
['generalSectionLinks', result.index, 'isDisplayed'],
result.hasPermission
);
Object.keys(action.data).forEach(sectionName => {
const sectionData = action.data[sectionName];
sectionData.forEach(result => {
set(draftState, [sectionName, result.index, 'isDisplayed'], result.hasPermission);
});
});
break;
}

View File

@ -0,0 +1,139 @@
import init, { sortLinks } from '../init';
describe('ADMIN | LeftMenu | init', () => {
describe('init', () => {
it('should return the initialState if the plugins are empty', () => {
const initialState = {
ok: true,
};
expect(init(initialState)).toEqual({ ok: true });
});
it('should create the pluginsSectionLinks correctly', () => {
const plugins = {
documentation: {
menu: {
pluginsSectionLinks: [
{
destination: '/plugins/documentation',
icon: 'doc',
label: {
id: 'documentation.plugin.name',
defaultMessage: 'Documentation',
},
name: 'documentation',
permissions: [{ action: 'plugins::documentation.read', subject: null }],
},
{
destination: '/plugins/documentation/test',
icon: 'doc',
label: {
id: 'documentation.plugin.name.test',
defaultMessage: 'Documentation Test',
},
name: 'documentation test',
permissions: [],
},
],
},
},
test: {},
'content-type-builder': {
menu: {
pluginsSectionLinks: [
{
destination: '/plugins/content-type-builder',
icon: 'plug',
label: {
id: 'content-type-builder.plugin.name',
defaultMessage: 'content-type-builder',
},
name: 'content-type-builder',
permissions: [{ action: 'plugins::content-type-builder.read', subject: null }],
},
],
},
},
};
const initialState = {
generalSectionLinks: [],
pluginsSectionLinks: [],
isLoading: true,
};
const expected = {
generalSectionLinks: [],
pluginsSectionLinks: [
{
destination: '/plugins/content-type-builder',
icon: 'plug',
label: {
id: 'content-type-builder.plugin.name',
defaultMessage: 'content-type-builder',
},
isDisplayed: false,
permissions: [{ action: 'plugins::content-type-builder.read', subject: null }],
},
{
destination: '/plugins/documentation',
icon: 'doc',
label: {
id: 'documentation.plugin.name',
defaultMessage: 'Documentation',
},
isDisplayed: false,
permissions: [{ action: 'plugins::documentation.read', subject: null }],
},
{
destination: '/plugins/documentation/test',
icon: 'doc',
label: {
id: 'documentation.plugin.name.test',
defaultMessage: 'Documentation Test',
},
isDisplayed: false,
permissions: [],
},
],
isLoading: true,
};
expect(init(initialState, plugins)).toEqual(expected);
});
});
describe('sortLinks', () => {
it('should return an empty array', () => {
expect(sortLinks([])).toEqual([]);
});
it('should return a sorted array', () => {
const data = [
{
name: 'un',
},
{ name: 'deux' },
{ name: 'un-un' },
{ name: 'un-deux' },
{ name: 'un un' },
];
const expected = [
{
name: 'deux',
},
{
name: 'un',
},
{ name: 'un un' },
{
name: 'un-deux',
},
{
name: 'un-un',
},
];
expect(sortLinks(data)).toEqual(expected);
});
});
});

View File

@ -62,23 +62,59 @@ describe('ADMIN | LeftMenu | reducer', () => {
permissions: [],
},
],
pluginsSectionLinks: [
{
destination: '/plugins/content-type-builder',
icon: 'paint-brush',
isDisplayed: false,
label: {
id: 'content-type-builder.plugin.name',
defaultMessage: 'Content-Types Builder',
},
permissions: [
{
action: 'plugins::content-type-builder.read',
subject: null,
},
],
},
{
destination: '/plugins/documentation',
icon: 'book',
isDisplayed: false,
label: { id: 'documentation.plugin.name', defaultMessage: 'Documentation' },
permissions: [
{ action: 'plugins::documentation.read', subject: null },
{ action: 'plugins::documentation.regenerate', subject: null },
{ action: 'plugins::documentation.update', subject: null },
],
},
],
};
const action = {
type: 'SET_LINK_PERMISSIONS',
results: [
{
index: 1,
hasPermission: true,
},
{
index: 0,
hasPermission: false,
},
{
index: 2,
hasPermission: true,
},
],
data: {
generalSectionLinks: [
{
index: 1,
hasPermission: true,
},
{
index: 0,
hasPermission: false,
},
{
index: 2,
hasPermission: true,
},
],
pluginsSectionLinks: [
{
index: 0,
hasPermission: true,
},
],
},
};
const expected = {
@ -112,6 +148,34 @@ describe('ADMIN | LeftMenu | reducer', () => {
permissions: [],
},
],
pluginsSectionLinks: [
{
destination: '/plugins/content-type-builder',
icon: 'paint-brush',
isDisplayed: true,
label: {
id: 'content-type-builder.plugin.name',
defaultMessage: 'Content-Types Builder',
},
permissions: [
{
action: 'plugins::content-type-builder.read',
subject: null,
},
],
},
{
destination: '/plugins/documentation',
icon: 'book',
isDisplayed: false,
label: { id: 'documentation.plugin.name', defaultMessage: 'Documentation' },
permissions: [
{ action: 'plugins::documentation.read', subject: null },
{ action: 'plugins::documentation.regenerate', subject: null },
{ action: 'plugins::documentation.update', subject: null },
],
},
],
};
expect(reducer(state, action)).toEqual(expected);

View File

@ -6,6 +6,7 @@
import React, { memo } from 'react';
import PropTypes from 'prop-types';
import { Redirect } from 'react-router-dom';
import { get } from 'lodash';
import { BlockerComponent } from 'strapi-helper-plugin';
@ -25,7 +26,7 @@ export function PluginDispatcher(props) {
const pluginToRender = get(plugins, pluginId, null);
if (!pluginToRender) {
return null;
return <Redirect to="/404" />;
}
const {
@ -35,9 +36,7 @@ export function PluginDispatcher(props) {
name,
preventComponentRendering,
} = pluginToRender;
let PluginEntryComponent = preventComponentRendering
? BlockerComponent
: mainComponent;
let PluginEntryComponent = preventComponentRendering ? BlockerComponent : mainComponent;
// Change the plugin's blockerComponent if the plugin uses a custom one.
if (preventComponentRendering && blockerComponent) {

View File

@ -23,14 +23,15 @@ window.strapi = Object.assign(window.strapi || {}, {
});
module.exports = {
'strapi-plugin-documentation': require('../../../strapi-plugin-documentation/admin/src').default,
'strapi-plugin-users-permissions': require('../../../strapi-plugin-users-permissions/admin/src')
.default,
'strapi-plugin-content-manager': require('../../../strapi-plugin-content-manager/admin/src')
.default,
'strapi-plugin-content-type-builder': require('../../../strapi-plugin-content-type-builder/admin/src')
.default,
'strapi-plugin-documentation': require('../../../strapi-plugin-documentation/admin/src').default,
'strapi-plugin-email': require('../../../strapi-plugin-email/admin/src').default,
'strapi-plugin-upload': require('../../../strapi-plugin-upload/admin/src').default,
// 'strapi-plugin-upload': require('../../../strapi-plugin-upload/admin/src').default,
'strapi-plugin-graphql': require('../../../strapi-plugin-graphql/admin/src').default,
};

View File

@ -354,12 +354,12 @@ const data = {
// },
// Upload plugin
{
action: 'plugins::upload.read',
subject: null,
fields: null,
conditions: [],
},
// {
// action: 'plugins::upload.read',
// subject: null,
// fields: null,
// conditions: [],
// },
{
action: 'plugins::upload.assets.create',
subject: null,

View File

@ -7,12 +7,14 @@ import trads from './translations';
export default strapi => {
const pluginDescription = pluginPkg.strapi.description || pluginPkg.description;
const icon = pluginPkg.strapi.icon;
const name = pluginPkg.strapi.name;
const plugin = {
blockerComponent: null,
blockerComponentProps: {},
description: pluginDescription,
icon: pluginPkg.strapi.icon,
icon,
id: pluginId,
initializer: Initializer,
injectedComponents: [],
@ -23,9 +25,29 @@ export default strapi => {
leftMenuLinks: [],
leftMenuSections: [],
mainComponent: App,
name: pluginPkg.strapi.name,
name,
preventComponentRendering: false,
trads,
menu: {
pluginsSection: [
{
destination: `/plugins/${pluginId}`,
icon,
label: {
id: `${pluginId}.plugin.name`,
defaultMessage: name,
},
name,
permissions: [
// Uncomment to set the permissions of the plugin here
// {
// action: '', // the action name should be plugins::plugin-name.actionType
// subject: null,
// },
],
},
],
},
};
return strapi.registerPlugin(plugin);

View File

@ -43,8 +43,6 @@ export default strapi => {
pluginLogo,
preventComponentRendering: false,
reducers,
suffixUrl: () => '/ctm-configurations/models',
suffixUrlToReplaceForLeftMenuHighlight: '/models',
trads,
};

View File

@ -17,11 +17,13 @@ import pluginId from './pluginId';
export default strapi => {
const pluginDescription = pluginPkg.strapi.description || pluginPkg.description;
const icon = pluginPkg.strapi.icon;
const name = pluginPkg.strapi.name;
const plugin = {
blockerComponent: null,
blockerComponentProps: {},
description: pluginDescription,
icon: pluginPkg.strapi.icon,
icon,
id: pluginId,
initializer: Initializer,
injectedComponents: [
@ -50,10 +52,24 @@ export default strapi => {
leftMenuLinks: [],
leftMenuSections: [],
mainComponent: App,
name: pluginPkg.strapi.name,
name,
pluginLogo,
preventComponentRendering: false,
trads,
menu: {
pluginsSectionLinks: [
{
destination: `/plugins/${pluginId}`,
icon,
label: {
id: `${pluginId}.plugin.name`,
defaultMessage: 'Content-Types Builder',
},
name,
permissions: [{ action: 'plugins::content-type-builder.read', subject: null }],
},
],
},
};
return strapi.registerPlugin(plugin);

View File

@ -176,5 +176,6 @@
"relation.oneToOne": "has and belongs to one",
"relation.oneWay": "has one",
"table.attributes.title.plural": "{number} fields",
"table.attributes.title.singular": "{number} field"
"table.attributes.title.singular": "{number} field",
"plugin.name": "Content-Types Builder"
}

View File

@ -16,11 +16,13 @@ import trads from './translations';
export default strapi => {
const pluginDescription = pluginPkg.strapi.description || pluginPkg.description;
const icon = pluginPkg.strapi.icon;
const name = pluginPkg.strapi.name;
const plugin = {
blockerComponent: null,
blockerComponentProps: {},
description: pluginDescription,
icon: pluginPkg.strapi.icon,
icon,
id: pluginId,
initializer: Initializer,
injectedComponents: [],
@ -31,11 +33,29 @@ export default strapi => {
leftMenuLinks: [],
leftMenuSections: [],
mainComponent: App,
name: pluginPkg.strapi.name,
name,
pluginLogo,
preventComponentRendering: false,
reducers,
trads,
menu: {
pluginsSectionLinks: [
{
destination: `/plugins/${pluginId}`,
icon,
label: {
id: `${pluginId}.plugin.name`,
defaultMessage: 'Documentation',
},
name,
permissions: [
{ action: 'plugins::documentation.read', subject: null },
{ action: 'plugins::documentation.regenerate', subject: null },
{ action: 'plugins::documentation.update', subject: null },
],
},
],
},
};
return strapi.registerPlugin(plugin);

View File

@ -25,5 +25,6 @@
"error.noVersion": "A version is required",
"error.regenerateDoc.versionMissing": "The version you are trying to generate doesn't exist",
"error.deleteDoc.versionMissing": "The version you are trying to delete does not exist.",
"notification.update.success": "Settings updated successfully"
"notification.update.success": "Settings updated successfully",
"plugin.name": "Documentation"
}

View File

@ -19,12 +19,14 @@ import { getTrad } from './utils';
export default strapi => {
const pluginDescription = pluginPkg.strapi.description || pluginPkg.description;
const icon = pluginPkg.strapi.icon;
const name = pluginPkg.strapi.name;
const plugin = {
blockerComponent: null,
blockerComponentProps: {},
description: pluginDescription,
fileModel: null,
icon: pluginPkg.strapi.icon,
icon,
id: pluginId,
initializer: Initializer,
injectedComponents: [],
@ -35,7 +37,7 @@ export default strapi => {
leftMenuLinks: [],
leftMenuSections: [],
mainComponent: App,
name: pluginPkg.strapi.name,
name,
pluginLogo,
preventComponentRendering: false,
settings: {
@ -52,6 +54,20 @@ export default strapi => {
],
},
trads,
menu: {
pluginsSectionLinks: [
{
destination: `/plugins/${pluginId}`,
icon,
label: {
id: `${pluginId}.plugin.name`,
defaultMessage: 'Media Library',
},
name,
permissions: [{ action: 'plugins::upload.read', subject: null }],
},
],
},
};
strapi.registerComponent({ name: 'media-library', Component: InputModalStepper });

View File

@ -6,7 +6,7 @@
*/
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import { Switch, Redirect, Route, useRouteMatch } from 'react-router-dom';
import { NotFound } from 'strapi-helper-plugin';
import pluginId from '../../pluginId';
@ -14,6 +14,13 @@ import EditPage from '../EditPage';
import HomePage from '../HomePage';
const App = () => {
const settingType = useRouteMatch(`/plugins/${pluginId}/:settingType`);
// Todo check if the settingType is allowed
if (!settingType) {
return <Redirect to={`/plugins/${pluginId}/roles`} />;
}
return (
<div className={pluginId}>
<Switch>
@ -22,11 +29,7 @@ const App = () => {
component={EditPage}
exact
/>
<Route
path={`/plugins/${pluginId}/:settingType`}
component={HomePage}
exact
/>
<Route path={`/plugins/${pluginId}/:settingType`} component={HomePage} exact />
<Route component={NotFound} />
</Switch>
</div>

View File

@ -17,12 +17,14 @@ import trads from './translations';
export default strapi => {
const pluginDescription = pluginPkg.strapi.description || pluginPkg.description;
const icon = pluginPkg.strapi.icon;
const name = pluginPkg.strapi.name;
const plugin = {
blockerComponent: null,
blockerComponentProps: {},
description: pluginDescription,
icon: pluginPkg.strapi.icon,
icon,
id: pluginId,
initializer: Initializer,
injectedComponents: [],
@ -32,14 +34,35 @@ export default strapi => {
leftMenuLinks: [],
leftMenuSections: [],
mainComponent: App,
name: pluginPkg.strapi.name,
name,
pluginLogo,
preventComponentRendering: false,
reducers,
settings: {},
suffixUrl: () => '/roles',
suffixUrlToReplaceForLeftMenuHighlight: '/roles',
trads,
menu: {
pluginsSectionLinks: [
{
destination: `/plugins/${pluginId}`,
icon,
label: {
id: `${pluginId}.plugin.name`,
defaultMessage: 'Roles & Permissions',
},
name,
permissions: [
{ action: 'plugins::users-permissions.advanced-settings.read', subject: null },
{ action: 'plugins::users-permissions.advanced-settings.update', subject: null },
{ action: 'plugins::users-permissions.email-templates.read', subject: null },
{ action: 'plugins::users-permissions.email-templates.update', subject: null },
{ action: 'plugins::users-permissions.providers.read', subject: null },
{ action: 'plugins::users-permissions.providers.update', subject: null },
{ action: 'plugins::users-permissions.roles.create', subject: null },
{ action: 'plugins::users-permissions.roles.read', subject: null },
],
},
],
},
};
return strapi.registerPlugin(plugin);

View File

@ -112,5 +112,6 @@
"notification.success.delete": "The item has been deleted",
"notification.success.submit": "Settings have been updated",
"plugin.description.long": "Protect your API with a full authentication process based on JWT. This plugin comes also with an ACL strategy that allows you to manage the permissions between the groups of users.",
"plugin.description.short": "Protect your API with a full authentication process based on JWT"
"plugin.description.short": "Protect your API with a full authentication process based on JWT",
"plugin.name": "Roles & Permission"
}