Various fixes in the UI.
Check permissions mechanism reworked to allow specifying permission checks already in menu structure.
This commit is contained in:
parent
a46c8fa9c3
commit
a258479621
37 changed files with 485 additions and 399 deletions
|
@ -22,7 +22,7 @@ import {
|
|||
withFormErrorHandlers
|
||||
} from '../lib/form';
|
||||
import {withAsyncErrorHandler, withErrorHandling} from '../lib/error-handling';
|
||||
import {NamespaceSelect, validateNamespace} from '../lib/namespace';
|
||||
import {getDefaultNamespace, NamespaceSelect, validateNamespace} from '../lib/namespace';
|
||||
import {DeleteModalDialog} from "../lib/modals";
|
||||
import mailtrainConfig from 'mailtrainConfig';
|
||||
import {getTagLanguages, getTemplateTypes, getTypeForm, ResourceType} from '../templates/helpers';
|
||||
|
@ -109,6 +109,7 @@ export default class CUD extends Component {
|
|||
static propTypes = {
|
||||
action: PropTypes.string.isRequired,
|
||||
entity: PropTypes.object,
|
||||
permissions: PropTypes.object,
|
||||
type: PropTypes.number
|
||||
}
|
||||
|
||||
|
@ -176,7 +177,12 @@ export default class CUD extends Component {
|
|||
}
|
||||
|
||||
for (const overridable of campaignOverridables) {
|
||||
data[overridable + '_overriden'] = data[overridable + '_override'] !== null;
|
||||
if (data[overridable + '_override'] === null) {
|
||||
data[overridable + '_override'] = '';
|
||||
data[overridable + '_overriden'] = false;
|
||||
} else {
|
||||
data[overridable + '_overriden'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
const lsts = [];
|
||||
|
@ -297,7 +303,7 @@ export default class CUD extends Component {
|
|||
lists: [lstUid],
|
||||
|
||||
send_configuration: null,
|
||||
namespace: mailtrainConfig.user.namespace,
|
||||
namespace: getDefaultNamespace(this.props.permissions),
|
||||
|
||||
subject: '',
|
||||
|
||||
|
|
|
@ -4,15 +4,15 @@ import React, {Component} from 'react';
|
|||
import {withTranslation} from '../lib/i18n';
|
||||
import {ButtonDropdown, Icon} from '../lib/bootstrap-components';
|
||||
import {DropdownLink, requiresAuthenticatedUser, Title, Toolbar, withPageHelpers} from '../lib/page';
|
||||
import {withAsyncErrorHandler, withErrorHandling} from '../lib/error-handling';
|
||||
import {withErrorHandling} from '../lib/error-handling';
|
||||
import {Table} from '../lib/table';
|
||||
import moment from 'moment';
|
||||
import {CampaignSource, CampaignStatus, CampaignType} from "../../../shared/campaigns";
|
||||
import {checkPermissions} from "../lib/permissions";
|
||||
import {getCampaignLabels} from "./helpers";
|
||||
import {tableAddDeleteButton, tableRestActionDialogInit, tableRestActionDialogRender} from "../lib/modals";
|
||||
import {withComponentMixins} from "../lib/decorator-helpers";
|
||||
import styles from "./styles.scss";
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
@withComponentMixins([
|
||||
withTranslation,
|
||||
|
@ -34,28 +34,16 @@ export default class List extends Component {
|
|||
tableRestActionDialogInit(this);
|
||||
}
|
||||
|
||||
@withAsyncErrorHandler
|
||||
async fetchPermissions() {
|
||||
const result = await checkPermissions({
|
||||
createCampaign: {
|
||||
entityTypeId: 'namespace',
|
||||
requiredOperations: ['createCampaign']
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({
|
||||
createPermitted: result.data.createCampaign
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.fetchPermissions();
|
||||
static propTypes = {
|
||||
permissions: PropTypes.object
|
||||
}
|
||||
|
||||
render() {
|
||||
const t = this.props.t;
|
||||
|
||||
const permissions = this.props.permissions;
|
||||
const createPermitted = permissions.createCampaign;
|
||||
|
||||
const columns = [
|
||||
{ data: 1, title: t('name') },
|
||||
{ data: 2, title: t('id'), render: data => <code>{data}</code>, className: styles.tblCol_id },
|
||||
|
@ -153,7 +141,7 @@ export default class List extends Component {
|
|||
<div>
|
||||
{tableRestActionDialogRender(this)}
|
||||
<Toolbar>
|
||||
{this.state.createPermitted &&
|
||||
{createPermitted &&
|
||||
<ButtonDropdown buttonClassName="btn-primary" menuClassName="dropdown-menu-right" label={t('createCampaign')}>
|
||||
<DropdownLink to="/campaigns/create-regular">{t('regular')}</DropdownLink>
|
||||
<DropdownLink to="/campaigns/create-rss">{t('rss')}</DropdownLink>
|
||||
|
|
|
@ -17,6 +17,7 @@ import {SubscriptionStatus} from "../../../shared/lists";
|
|||
import StatisticsOpened from "./StatisticsOpened";
|
||||
import StatisticsLinkClicks from "./StatisticsLinkClicks";
|
||||
import {ellipsizeBreadcrumbLabel} from "../lib/helpers"
|
||||
import {namespaceCheckPermissions} from "../lib/namespace";
|
||||
|
||||
function getMenus(t) {
|
||||
const aggLabels = {
|
||||
|
@ -28,7 +29,14 @@ function getMenus(t) {
|
|||
'campaigns': {
|
||||
title: t('campaigns'),
|
||||
link: '/campaigns',
|
||||
panelComponent: CampaignsList,
|
||||
checkPermissions: {
|
||||
createCampaign: {
|
||||
entityTypeId: 'namespace',
|
||||
requiredOperations: ['createCampaign']
|
||||
},
|
||||
...namespaceCheckPermissions('createCampaign')
|
||||
},
|
||||
panelRender: props => <CampaignsList permissions={props.permissions}/>,
|
||||
children: {
|
||||
':campaignId([0-9]+)': {
|
||||
title: resolved => t('campaignName', {name: ellipsizeBreadcrumbLabel(resolved.campaign.name)}),
|
||||
|
@ -94,7 +102,7 @@ function getMenus(t) {
|
|||
title: t('edit'),
|
||||
link: params => `/campaigns/${params.campaignId}/edit`,
|
||||
visible: resolved => resolved.campaign.permissions.includes('edit'),
|
||||
panelRender: props => <CampaignsCUD action={props.match.params.action} entity={props.resolved.campaign} />
|
||||
panelRender: props => <CampaignsCUD action={props.match.params.action} entity={props.resolved.campaign} permissions={props.permissions} />
|
||||
},
|
||||
content: {
|
||||
title: t('content'),
|
||||
|
@ -153,15 +161,15 @@ function getMenus(t) {
|
|||
},
|
||||
'create-regular': {
|
||||
title: t('createRegularCampaign'),
|
||||
panelRender: props => <CampaignsCUD action="create" type={CampaignType.REGULAR} />
|
||||
panelRender: props => <CampaignsCUD action="create" type={CampaignType.REGULAR} permissions={props.permissions} />
|
||||
},
|
||||
'create-rss': {
|
||||
title: t('createRssCampaign'),
|
||||
panelRender: props => <CampaignsCUD action="create" type={CampaignType.RSS} />
|
||||
panelRender: props => <CampaignsCUD action="create" type={CampaignType.RSS} permissions={props.permissions} />
|
||||
},
|
||||
'create-triggered': {
|
||||
title: t('createTriggeredCampaign'),
|
||||
panelRender: props => <CampaignsCUD action="create" type={CampaignType.TRIGGERED} />
|
||||
panelRender: props => <CampaignsCUD action="create" type={CampaignType.TRIGGERED} permissions={props.permissions} />
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -818,7 +818,7 @@ class TreeTableSelect extends Component {
|
|||
dataUrl: PropTypes.string,
|
||||
data: PropTypes.array,
|
||||
help: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
format: PropTypes.string
|
||||
format: PropTypes.string,
|
||||
}
|
||||
|
||||
async onSelectionChangedAsync(sel) {
|
||||
|
@ -835,7 +835,7 @@ class TreeTableSelect extends Component {
|
|||
const className = owner.addFormValidationClass('' , id);
|
||||
|
||||
return wrapInput(id, htmlId, owner, props.format, '', props.label, props.help,
|
||||
<TreeTable className={className} data={props.data} dataUrl={props.dataUrl} selectMode={TreeSelectMode.SINGLE} selection={owner.getFormValue(id)} onSelectionChangedAsync={::this.onSelectionChangedAsync}/>
|
||||
<TreeTable className={className} data={props.data} dataUrl={props.dataUrl} selectMode={TreeSelectMode.SINGLE} selection={owner.getFormValue(id)} onSelectionChangedAsync={::this.onSelectionChangedAsync} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,7 +89,7 @@ export class RestActionModalDialog extends Component {
|
|||
const t = this.props.t;
|
||||
|
||||
return (
|
||||
<ModalDialog hidden={!this.props.visible} title={this.props.title} onCloseAsync={() => this.hideModal(true)} buttons={[
|
||||
<ModalDialog hidden={!this.props.visible} title={this.props.title} onCloseAsync={async () => await this.hideModal(true)} buttons={[
|
||||
{ label: t('no'), className: 'btn-primary', onClickAsync: async () => await this.hideModal(true) },
|
||||
{ label: t('yes'), className: 'btn-danger', onClickAsync: ::this.performAction }
|
||||
]}>
|
||||
|
@ -115,16 +115,23 @@ const entityTypeLabels = {
|
|||
function _getDependencyErrorMessage(err, t, name) {
|
||||
return (
|
||||
<div>
|
||||
<p>{t('cannoteDeleteNameDueToTheFollowing', {name})}</p>
|
||||
<ul className={styles.errorsList}>
|
||||
{err.data.dependencies.map(dep =>
|
||||
dep.link ?
|
||||
<li key={dep.link}><Link to={dep.link}>{entityTypeLabels[dep.entityTypeId](t)}: {dep.name}</Link></li>
|
||||
: // if no dep.link is present, it means the user has no permission to view the entity, thus only id without the link is shown
|
||||
<li key={dep.id}>{entityTypeLabels[dep.entityTypeId](t)}: [{dep.id}]</li>
|
||||
)}
|
||||
{err.data.andMore && <li>{t('andMore')}</li>}
|
||||
</ul>
|
||||
{err.data.dependencies.length > 0 ?
|
||||
<>
|
||||
<p>{t('cannoteDeleteNameDueToTheFollowing', {name})}</p>
|
||||
<ul className={styles.errorsList}>
|
||||
{err.data.dependencies.map(dep =>
|
||||
dep.link ?
|
||||
<li key={dep.link}><Link
|
||||
to={dep.link}>{entityTypeLabels[dep.entityTypeId](t)}: {dep.name}</Link></li>
|
||||
: // if no dep.link is present, it means the user has no permission to view the entity, thus only id without the link is shown
|
||||
<li key={dep.id}>{entityTypeLabels[dep.entityTypeId](t)}: [{dep.id}]</li>
|
||||
)}
|
||||
{err.data.andMore && <li>{t('andMore')}</li>}
|
||||
</ul>
|
||||
</>
|
||||
:
|
||||
<p>{t('Cannot delete {{name}} due to hidden dependencies', {name})}</p>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -144,10 +151,11 @@ export class DeleteModalDialog extends Component {
|
|||
visible: PropTypes.bool.isRequired,
|
||||
stateOwner: PropTypes.object.isRequired,
|
||||
deleteUrl: PropTypes.string.isRequired,
|
||||
backUrl: PropTypes.string,
|
||||
successUrl: PropTypes.string,
|
||||
backUrl: PropTypes.string.isRequired,
|
||||
successUrl: PropTypes.string.isRequired,
|
||||
deletingMsg: PropTypes.string.isRequired,
|
||||
deletedMsg: PropTypes.string.isRequired
|
||||
deletedMsg: PropTypes.string.isRequired,
|
||||
name: PropTypes.string
|
||||
}
|
||||
|
||||
async onErrorAsync(err) {
|
||||
|
@ -172,7 +180,7 @@ export class DeleteModalDialog extends Component {
|
|||
render() {
|
||||
const t = this.props.t;
|
||||
const owner = this.props.stateOwner;
|
||||
const name = owner.getFormValue('name') || '';
|
||||
const name = this.props.name || owner.getFormValue('name') || '';
|
||||
|
||||
return <RestActionModalDialog
|
||||
title={t('confirmDeletion')}
|
||||
|
|
|
@ -9,7 +9,7 @@ import {withComponentMixins} from "./decorator-helpers";
|
|||
@withComponentMixins([
|
||||
withTranslation
|
||||
])
|
||||
class NamespaceSelect extends Component {
|
||||
export class NamespaceSelect extends Component {
|
||||
render() {
|
||||
const t = this.props.t;
|
||||
|
||||
|
@ -19,7 +19,7 @@ class NamespaceSelect extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
function validateNamespace(t, state) {
|
||||
export function validateNamespace(t, state) {
|
||||
if (!state.getIn(['namespace', 'value'])) {
|
||||
state.setIn(['namespace', 'error'], t('namespaceMustBeSelected'));
|
||||
} else {
|
||||
|
@ -27,7 +27,21 @@ function validateNamespace(t, state) {
|
|||
}
|
||||
}
|
||||
|
||||
export {
|
||||
NamespaceSelect,
|
||||
validateNamespace
|
||||
};
|
||||
export function getDefaultNamespace(permissions) {
|
||||
return permissions.viewUsersNamespace && permissions.createEntityInUsersNamespace ? mailtrainConfig.user.namespace : null;
|
||||
}
|
||||
|
||||
export function namespaceCheckPermissions(createOperation) {
|
||||
return {
|
||||
createEntityInUsersNamespace: {
|
||||
entityTypeId: 'namespace',
|
||||
entityId: mailtrainConfig.user.namespace,
|
||||
requiredOperations: [createOperation]
|
||||
},
|
||||
viewUsersNamespace: {
|
||||
entityTypeId: 'namespace',
|
||||
entityId: mailtrainConfig.user.namespace,
|
||||
requiredOperations: ['view']
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -9,94 +9,142 @@ import {getUrl} from "./urls";
|
|||
import {createComponentMixin, withComponentMixins} from "./decorator-helpers";
|
||||
import {withTranslation} from "./i18n";
|
||||
import shallowEqual from "shallowequal";
|
||||
import {checkPermissions} from "./permissions";
|
||||
|
||||
async function resolve(route, match, prevResolvedByUrl) {
|
||||
async function resolve(route, match, prevResolverState) {
|
||||
const resolved = {};
|
||||
const resolvedByUrl = {};
|
||||
const keysToGo = new Set(Object.keys(route.resolve));
|
||||
const permissions = {};
|
||||
const resolverState = {
|
||||
resolvedByUrl: {},
|
||||
permissionsBySig: {}
|
||||
};
|
||||
|
||||
prevResolvedByUrl = prevResolvedByUrl || {};
|
||||
prevResolverState = prevResolverState || {
|
||||
resolvedByUrl: {},
|
||||
permissionsBySig: {}
|
||||
};
|
||||
|
||||
while (keysToGo.size > 0) {
|
||||
const urlsToResolve = [];
|
||||
const keysToResolve = [];
|
||||
async function processResolve() {
|
||||
const keysToGo = new Set(Object.keys(route.resolve));
|
||||
|
||||
for (const key of keysToGo) {
|
||||
const resolveEntry = route.resolve[key];
|
||||
while (keysToGo.size > 0) {
|
||||
const urlsToResolve = [];
|
||||
const keysToResolve = [];
|
||||
|
||||
let allDepsSatisfied = true;
|
||||
let urlFn = null;
|
||||
for (const key of keysToGo) {
|
||||
const resolveEntry = route.resolve[key];
|
||||
|
||||
if (typeof resolveEntry === 'function') {
|
||||
urlFn = resolveEntry;
|
||||
let allDepsSatisfied = true;
|
||||
let urlFn = null;
|
||||
|
||||
} else {
|
||||
if (resolveEntry.dependencies) {
|
||||
for (const dep of resolveEntry.dependencies) {
|
||||
if (!(dep in resolved)) {
|
||||
allDepsSatisfied = false;
|
||||
break;
|
||||
if (typeof resolveEntry === 'function') {
|
||||
urlFn = resolveEntry;
|
||||
|
||||
} else {
|
||||
if (resolveEntry.dependencies) {
|
||||
for (const dep of resolveEntry.dependencies) {
|
||||
if (!(dep in resolved)) {
|
||||
allDepsSatisfied = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
urlFn = resolveEntry.url;
|
||||
}
|
||||
|
||||
urlFn = resolveEntry.url;
|
||||
if (allDepsSatisfied) {
|
||||
urlsToResolve.push(urlFn(match.params, resolved));
|
||||
keysToResolve.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
if (allDepsSatisfied) {
|
||||
urlsToResolve.push(urlFn(match.params, resolved));
|
||||
keysToResolve.push(key);
|
||||
if (keysToResolve.length === 0) {
|
||||
throw new Error('Cyclic dependency in "resolved" entries of ' + route.path);
|
||||
}
|
||||
}
|
||||
|
||||
if (keysToResolve.length === 0) {
|
||||
throw new Error('Cyclic dependency in "resolved" entries of ' + route.path);
|
||||
}
|
||||
const urlsToResolveByRest = [];
|
||||
const keysToResolveByRest = [];
|
||||
|
||||
const urlsToResolveByRest = [];
|
||||
const keysToResolveByRest = [];
|
||||
for (let idx = 0; idx < keysToResolve.length; idx++) {
|
||||
const key = keysToResolve[idx];
|
||||
const url = urlsToResolve[idx];
|
||||
|
||||
for (let idx = 0; idx < keysToResolve.length; idx++) {
|
||||
const key = keysToResolve[idx];
|
||||
const url = urlsToResolve[idx];
|
||||
if (url in prevResolverState.resolvedByUrl) {
|
||||
const entity = prevResolverState.resolvedByUrl[url];
|
||||
resolved[key] = entity;
|
||||
resolverState.resolvedByUrl[url] = entity;
|
||||
|
||||
if (url in prevResolvedByUrl) {
|
||||
const entity = prevResolvedByUrl[url];
|
||||
resolved[key] = entity;
|
||||
resolvedByUrl[url] = entity;
|
||||
|
||||
} else {
|
||||
urlsToResolveByRest.push(url);
|
||||
keysToResolveByRest.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
if (keysToResolveByRest.length > 0) {
|
||||
const promises = urlsToResolveByRest.map(url => {
|
||||
if (url) {
|
||||
return axios.get(getUrl(url));
|
||||
} else {
|
||||
return Promise.resolve({data: null});
|
||||
urlsToResolveByRest.push(url);
|
||||
keysToResolveByRest.push(key);
|
||||
}
|
||||
});
|
||||
const resolvedArr = await Promise.all(promises);
|
||||
|
||||
for (let idx = 0; idx < keysToResolveByRest.length; idx++) {
|
||||
resolved[keysToResolveByRest[idx]] = resolvedArr[idx].data;
|
||||
resolvedByUrl[urlsToResolveByRest[idx]] = resolvedArr[idx].data;
|
||||
}
|
||||
}
|
||||
|
||||
for (const key of keysToResolve) {
|
||||
keysToGo.delete(key);
|
||||
if (keysToResolveByRest.length > 0) {
|
||||
const promises = urlsToResolveByRest.map(url => {
|
||||
if (url) {
|
||||
return axios.get(getUrl(url));
|
||||
} else {
|
||||
return Promise.resolve({data: null});
|
||||
}
|
||||
});
|
||||
const resolvedArr = await Promise.all(promises);
|
||||
|
||||
for (let idx = 0; idx < keysToResolveByRest.length; idx++) {
|
||||
resolved[keysToResolveByRest[idx]] = resolvedArr[idx].data;
|
||||
resolverState.resolvedByUrl[urlsToResolveByRest[idx]] = resolvedArr[idx].data;
|
||||
}
|
||||
}
|
||||
|
||||
for (const key of keysToResolve) {
|
||||
keysToGo.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { resolved, resolvedByUrl };
|
||||
async function processCheckPermissions() {
|
||||
const checkPermsRequest = {};
|
||||
|
||||
function getSig(checkPermissionsEntry) {
|
||||
return `${checkPermissionsEntry.entityTypeId}-${checkPermissionsEntry.entityId || ''}-${checkPermissionsEntry.requiredOperations.join(',')}`;
|
||||
}
|
||||
|
||||
for (const key in route.checkPermissions) {
|
||||
const checkPermissionsEntry = route.checkPermissions[key];
|
||||
const sig = getSig(checkPermissionsEntry);
|
||||
|
||||
if (sig in prevResolverState.permissionsBySig) {
|
||||
const perm = prevResolverState.permissionsBySig[sig];
|
||||
permissions[key] = perm;
|
||||
resolverState.permissionsBySig[sig] = perm;
|
||||
|
||||
} else {
|
||||
checkPermsRequest[key] = checkPermissionsEntry;
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(checkPermsRequest).length > 0) {
|
||||
const result = await checkPermissions(checkPermsRequest);
|
||||
|
||||
for (const key in checkPermsRequest) {
|
||||
const checkPermissionsEntry = checkPermsRequest[key];
|
||||
const perm = result.data[key];
|
||||
|
||||
permissions[key] = perm;
|
||||
resolverState.permissionsBySig[getSig(checkPermissionsEntry)] = perm;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
await Promise.all([processResolve(), processCheckPermissions()]);
|
||||
|
||||
return { resolved, permissions, resolverState };
|
||||
}
|
||||
|
||||
export function getRoutes(structure, parentRoute) {
|
||||
function _getRoutes(urlPrefix, resolve, parents, structure, navs, primaryMenuComponent, secondaryMenuComponent) {
|
||||
function _getRoutes(urlPrefix, resolve, checkPermissions, parents, structure, navs, primaryMenuComponent, secondaryMenuComponent) {
|
||||
let routes = [];
|
||||
for (let routeKey in structure) {
|
||||
const entry = structure[routeKey];
|
||||
|
@ -115,6 +163,13 @@ export function getRoutes(structure, parentRoute) {
|
|||
entryResolve = resolve;
|
||||
}
|
||||
|
||||
let entryCheckPermissions;
|
||||
if (entry.checkPermissions) {
|
||||
entryCheckPermissions = Object.assign({}, checkPermissions, entry.checkPermissions);
|
||||
} else {
|
||||
entryCheckPermissions = checkPermissions;
|
||||
}
|
||||
|
||||
let navKeys;
|
||||
const entryNavs = [];
|
||||
if (entry.navs) {
|
||||
|
@ -145,6 +200,7 @@ export function getRoutes(structure, parentRoute) {
|
|||
panelInFullScreen: entry.panelInFullScreen,
|
||||
insideIframe: entry.insideIframe,
|
||||
resolve: entryResolve,
|
||||
checkPermissions: entryCheckPermissions,
|
||||
parents,
|
||||
navs: [...navs, ...entryNavs],
|
||||
|
||||
|
@ -167,12 +223,12 @@ export function getRoutes(structure, parentRoute) {
|
|||
const childNavs = [...entryNavs];
|
||||
childNavs[navKeyIdx] = Object.assign({}, childNavs[navKeyIdx], { active: true });
|
||||
|
||||
routes = routes.concat(_getRoutes(path + '/', entryResolve, childrenParents, { [navKey]: nav }, childNavs, route.primaryMenuComponent, route.secondaryMenuComponent));
|
||||
routes = routes.concat(_getRoutes(path + '/', entryResolve, entryCheckPermissions, childrenParents, { [navKey]: nav }, childNavs, route.primaryMenuComponent, route.secondaryMenuComponent));
|
||||
}
|
||||
}
|
||||
|
||||
if (entry.children) {
|
||||
routes = routes.concat(_getRoutes(path + '/', entryResolve, childrenParents, entry.children, entryNavs, route.primaryMenuComponent, route.secondaryMenuComponent));
|
||||
routes = routes.concat(_getRoutes(path + '/', entryResolve, entryCheckPermissions, childrenParents, entry.children, entryNavs, route.primaryMenuComponent, route.secondaryMenuComponent));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -192,10 +248,10 @@ export function getRoutes(structure, parentRoute) {
|
|||
children: { ...(routeSpec.children || {}), ...(structure.children || {}) }
|
||||
};
|
||||
|
||||
return _getRoutes(parentRoute.urlPrefix, parentRoute.resolve, parentRoute.parents, { [parentRoute.routeKey]: extStructure }, parentRoute.siblingNavs, parentRoute.primaryMenuComponent, parentRoute.secondaryMenuComponent);
|
||||
return _getRoutes(parentRoute.urlPrefix, parentRoute.resolve, parentRoute.checkPermissions, parentRoute.parents, { [parentRoute.routeKey]: extStructure }, parentRoute.siblingNavs, parentRoute.primaryMenuComponent, parentRoute.secondaryMenuComponent);
|
||||
|
||||
} else {
|
||||
return _getRoutes('', {}, [], { "": structure }, [], null, null);
|
||||
return _getRoutes('', {}, {}, [], { "": structure }, [], null, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -209,11 +265,13 @@ export class Resolver extends Component {
|
|||
|
||||
this.state = {
|
||||
resolved: null,
|
||||
resolvedByUrl: null
|
||||
permissions: null,
|
||||
resolverState: null
|
||||
};
|
||||
|
||||
if (Object.keys(props.route.resolve).length === 0) {
|
||||
if (Object.keys(props.route.resolve).length === 0 && Object.keys(props.route.checkPermissions).length === 0) {
|
||||
this.state.resolved = {};
|
||||
this.state.permissions = {};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -228,28 +286,31 @@ export class Resolver extends Component {
|
|||
async resolve(prevMatch) {
|
||||
const props = this.props;
|
||||
|
||||
if (Object.keys(props.route.resolve).length === 0) {
|
||||
if (Object.keys(props.route.resolve).length === 0 && Object.keys(props.route.checkPermissions).length === 0) {
|
||||
this.setState({
|
||||
resolved: {},
|
||||
resolvedByUrl: {}
|
||||
permissions: {},
|
||||
resolverState: null
|
||||
});
|
||||
|
||||
} else {
|
||||
const prevResolvedByUrl = this.state.resolvedByUrl;
|
||||
const prevResolverState = this.state.resolverState;
|
||||
|
||||
if (this.state.resolved) {
|
||||
if (this.state.resolverState) {
|
||||
this.setState({
|
||||
resolved: null,
|
||||
resolvedByUrl: null
|
||||
permissions: null,
|
||||
resolverState: null
|
||||
});
|
||||
}
|
||||
|
||||
const {resolved, resolvedByUrl} = await resolve(props.route, props.match, prevResolvedByUrl);
|
||||
const {resolved, permissions, resolverState} = await resolve(props.route, props.match, prevResolverState);
|
||||
|
||||
if (!this.disregardResolve) { // This is to prevent the warning about setState on discarded component when we immediatelly redirect.
|
||||
this.setState({
|
||||
resolved,
|
||||
resolvedByUrl
|
||||
permissions,
|
||||
resolverState
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -272,7 +333,7 @@ export class Resolver extends Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
return this.props.render(this.state.resolved, this.props);
|
||||
return this.props.render(this.state.resolved, this.state.permissions, this.props);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -316,9 +377,9 @@ class SubRoute extends Component {
|
|||
const route = this.props.route;
|
||||
const params = this.props.match.params;
|
||||
|
||||
const render = resolved => {
|
||||
if (resolved) {
|
||||
const subStructure = route.structure(resolved, params);
|
||||
const render = (resolved, permissions) => {
|
||||
if (resolved && permissions) {
|
||||
const subStructure = route.structure(resolved, permissions, params);
|
||||
const routes = getRoutes(subStructure, route);
|
||||
|
||||
const _renderRoute = route => {
|
||||
|
|
|
@ -268,16 +268,17 @@ class PanelRoute extends Component {
|
|||
|
||||
const panelInFullScreen = this.state.panelInFullScreen;
|
||||
|
||||
const render = resolved => {
|
||||
const render = (resolved, permissions) => {
|
||||
let primaryMenu = null;
|
||||
let secondaryMenu = null;
|
||||
let content = null;
|
||||
|
||||
if (resolved) {
|
||||
if (resolved && permissions) {
|
||||
const compProps = {
|
||||
match: this.props.match,
|
||||
location: this.props.location,
|
||||
resolved,
|
||||
permissions,
|
||||
setPanelInFullScreen: this.setPanelInFullScreen,
|
||||
panelInFullScreen: this.state.panelInFullScreen
|
||||
};
|
||||
|
|
|
@ -3,10 +3,6 @@
|
|||
import {getUrl} from "./urls";
|
||||
import axios from "./axios";
|
||||
|
||||
async function checkPermissions(request) {
|
||||
export async function checkPermissions(request) {
|
||||
return await axios.post(getUrl('rest/permissions-check'), request);
|
||||
}
|
||||
|
||||
export {
|
||||
checkPermissions
|
||||
}
|
|
@ -36,7 +36,7 @@ class TreeTable extends Component {
|
|||
this.mounted = false;
|
||||
|
||||
this.state = {
|
||||
treeData: []
|
||||
treeData: null
|
||||
};
|
||||
|
||||
if (props.data) {
|
||||
|
@ -48,7 +48,7 @@ class TreeTable extends Component {
|
|||
}
|
||||
|
||||
static defaultProps = {
|
||||
selectMode: TreeSelectMode.NONE
|
||||
selectMode: TreeSelectMode.NONE
|
||||
}
|
||||
|
||||
refresh() {
|
||||
|
@ -99,16 +99,19 @@ class TreeTable extends Component {
|
|||
// XSS protection
|
||||
sanitizeTreeData(unsafeData) {
|
||||
const data = [];
|
||||
for (const unsafeEntry of unsafeData) {
|
||||
const entry = Object.assign({}, unsafeEntry);
|
||||
entry.unsanitizedTitle = entry.title;
|
||||
entry.title = ReactDOMServer.renderToStaticMarkup(<div>{entry.title}</div>);
|
||||
entry.description = ReactDOMServer.renderToStaticMarkup(<div>{entry.description}</div>);
|
||||
if (entry.children) {
|
||||
entry.children = this.sanitizeTreeData(entry.children);
|
||||
if (unsafeData) {
|
||||
for (const unsafeEntry of unsafeData) {
|
||||
const entry = Object.assign({}, unsafeEntry);
|
||||
entry.unsanitizedTitle = entry.title;
|
||||
entry.title = ReactDOMServer.renderToStaticMarkup(<div>{entry.title}</div>);
|
||||
entry.description = ReactDOMServer.renderToStaticMarkup(<div>{entry.description}</div>);
|
||||
if (entry.children) {
|
||||
entry.children = this.sanitizeTreeData(entry.children);
|
||||
}
|
||||
data.push(entry);
|
||||
}
|
||||
data.push(entry);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
@ -193,6 +196,7 @@ class TreeTable extends Component {
|
|||
createNode: createNodeFn,
|
||||
checkbox: this.selectMode === TreeSelectMode.MULTI,
|
||||
activate: (this.selectMode === TreeSelectMode.SINGLE ? ::this.onActivate : null),
|
||||
deactivate: (this.selectMode === TreeSelectMode.SINGLE ? ::this.onActivate : null),
|
||||
select: (this.selectMode === TreeSelectMode.MULTI ? ::this.onSelect : null),
|
||||
};
|
||||
|
||||
|
@ -241,7 +245,22 @@ class TreeTable extends Component {
|
|||
tree.enableUpdate(true);
|
||||
|
||||
} else if (this.selectMode === TreeSelectMode.SINGLE) {
|
||||
this.tree.activateKey(this.stringifyKey(this.props.selection));
|
||||
let selection = this.stringifyKey(this.props.selection);
|
||||
|
||||
if (this.state.treeData) {
|
||||
if (!tree.getNodeByKey(selection)) {
|
||||
selection = null;
|
||||
}
|
||||
|
||||
if (selection === null && !this.tree.getActiveNode()) {
|
||||
// This covers the case when we mount the tree and selection is not present in the tree.
|
||||
// At this point, nothing is selected, so the onActive event won't trigger. So we have to
|
||||
// call it manually, so that the form can update and set null instead of the invalid selection.
|
||||
this.onActivate();
|
||||
} else {
|
||||
tree.activateKey(selection);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -270,7 +289,8 @@ class TreeTable extends Component {
|
|||
|
||||
// Single-select
|
||||
onActivate(event, data) {
|
||||
const selection = this.destringifyKey(this.tree.getActiveNode().key);
|
||||
const activeNode = this.tree.getActiveNode();
|
||||
const selection = activeNode ? this.destringifyKey(activeNode.key) : null;
|
||||
|
||||
if (selection !== this.props.selection) {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
|
@ -298,7 +318,7 @@ class TreeTable extends Component {
|
|||
|
||||
if (updated) {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.onSelectionChanged(selection);
|
||||
this.onSelectionChanged(newSel);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,10 +22,9 @@ import {
|
|||
} from '../lib/form';
|
||||
import {withErrorHandling} from '../lib/error-handling';
|
||||
import {DeleteModalDialog} from '../lib/modals';
|
||||
import {NamespaceSelect, validateNamespace} from '../lib/namespace';
|
||||
import {getDefaultNamespace, NamespaceSelect, validateNamespace} from '../lib/namespace';
|
||||
import {FieldWizard, UnsubscriptionMode} from '../../../shared/lists';
|
||||
import styles from "../lib/styles.scss";
|
||||
import mailtrainConfig from 'mailtrainConfig';
|
||||
import {getMailerTypes} from "../send-configurations/helpers";
|
||||
import {withComponentMixins} from "../lib/decorator-helpers";
|
||||
|
||||
|
@ -49,7 +48,8 @@ export default class CUD extends Component {
|
|||
|
||||
static propTypes = {
|
||||
action: PropTypes.string.isRequired,
|
||||
entity: PropTypes.object
|
||||
entity: PropTypes.object,
|
||||
permissions: PropTypes.object
|
||||
}
|
||||
|
||||
getFormValuesMutator(data) {
|
||||
|
@ -86,7 +86,7 @@ export default class CUD extends Component {
|
|||
contact_email: '',
|
||||
homepage: '',
|
||||
unsubscription_mode: UnsubscriptionMode.ONE_STEP,
|
||||
namespace: mailtrainConfig.user.namespace,
|
||||
namespace: getDefaultNamespace(this.props.permissions),
|
||||
to_name: '',
|
||||
fieldWizard: FieldWizard.FIRST_LAST_NAME,
|
||||
send_configuration: null,
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
import React, {Component} from 'react';
|
||||
import {withTranslation} from '../lib/i18n';
|
||||
import {LinkButton, requiresAuthenticatedUser, Title, Toolbar, withPageHelpers} from '../lib/page';
|
||||
import {withAsyncErrorHandler, withErrorHandling} from '../lib/error-handling';
|
||||
import {withErrorHandling} from '../lib/error-handling';
|
||||
import {Table} from '../lib/table';
|
||||
import {Icon} from "../lib/bootstrap-components";
|
||||
import {checkPermissions} from "../lib/permissions";
|
||||
import {tableAddDeleteButton, tableRestActionDialogInit, tableRestActionDialogRender} from "../lib/modals";
|
||||
import {withComponentMixins} from "../lib/decorator-helpers";
|
||||
import {withForm} from "../lib/form";
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
@withComponentMixins([
|
||||
withTranslation,
|
||||
|
@ -26,28 +26,17 @@ export default class List extends Component {
|
|||
tableRestActionDialogInit(this);
|
||||
}
|
||||
|
||||
@withAsyncErrorHandler
|
||||
async fetchPermissions() {
|
||||
const result = await checkPermissions({
|
||||
createList: {
|
||||
entityTypeId: 'namespace',
|
||||
requiredOperations: ['createList']
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({
|
||||
createPermitted: result.data.createList
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.fetchPermissions();
|
||||
static propTypes = {
|
||||
permissions: PropTypes.object
|
||||
}
|
||||
|
||||
render() {
|
||||
const t = this.props.t;
|
||||
|
||||
const permissions = this.props.permissions;
|
||||
const createPermitted = permissions.createList;
|
||||
const customFormsPermitted = permissions.createCustomForm || permissions.viewCustomForm;
|
||||
|
||||
const columns = [
|
||||
{
|
||||
data: 1,
|
||||
|
@ -130,12 +119,14 @@ export default class List extends Component {
|
|||
return (
|
||||
<div>
|
||||
{tableRestActionDialogRender(this)}
|
||||
{this.state.createPermitted &&
|
||||
<Toolbar>
|
||||
<Toolbar>
|
||||
{ createPermitted &&
|
||||
<LinkButton to="/lists/create" className="btn-primary" icon="plus" label={t('createList')}/>
|
||||
}
|
||||
{ customFormsPermitted &&
|
||||
<LinkButton to="/lists/forms" className="btn-primary" label={t('customForms-1')}/>
|
||||
</Toolbar>
|
||||
}
|
||||
}
|
||||
</Toolbar>
|
||||
|
||||
<Title>{t('lists')}</Title>
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ import {
|
|||
withFormErrorHandlers
|
||||
} from '../../lib/form';
|
||||
import {withErrorHandling} from '../../lib/error-handling';
|
||||
import {NamespaceSelect, validateNamespace} from '../../lib/namespace';
|
||||
import {getDefaultNamespace, NamespaceSelect, validateNamespace} from '../../lib/namespace';
|
||||
import {DeleteModalDialog} from "../../lib/modals";
|
||||
import mailtrainConfig from 'mailtrainConfig';
|
||||
import {getTrustedUrl, getUrl} from "../../lib/urls";
|
||||
|
@ -280,7 +280,8 @@ export default class CUD extends Component {
|
|||
|
||||
static propTypes = {
|
||||
action: PropTypes.string.isRequired,
|
||||
entity: PropTypes.object
|
||||
entity: PropTypes.object,
|
||||
permissions: PropTypes.object
|
||||
}
|
||||
|
||||
|
||||
|
@ -337,7 +338,7 @@ export default class CUD extends Component {
|
|||
fromExistingEntity: false,
|
||||
existingEntity: null,
|
||||
selectedTemplate: 'layout',
|
||||
namespace: mailtrainConfig.user.namespace
|
||||
namespace: getDefaultNamespace(this.props.permissions)
|
||||
};
|
||||
this.supplyDefaults(data);
|
||||
|
||||
|
|
|
@ -3,12 +3,12 @@
|
|||
import React, {Component} from 'react';
|
||||
import {withTranslation} from '../../lib/i18n';
|
||||
import {LinkButton, requiresAuthenticatedUser, Title, Toolbar, withPageHelpers} from '../../lib/page';
|
||||
import {withAsyncErrorHandler, withErrorHandling} from '../../lib/error-handling';
|
||||
import {withErrorHandling} from '../../lib/error-handling';
|
||||
import {Table} from '../../lib/table';
|
||||
import {Icon} from "../../lib/bootstrap-components";
|
||||
import {checkPermissions} from "../../lib/permissions";
|
||||
import {tableAddDeleteButton, tableRestActionDialogInit, tableRestActionDialogRender} from "../../lib/modals";
|
||||
import {withComponentMixins} from "../../lib/decorator-helpers";
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
@withComponentMixins([
|
||||
withTranslation,
|
||||
|
@ -24,28 +24,16 @@ export default class List extends Component {
|
|||
tableRestActionDialogInit(this);
|
||||
}
|
||||
|
||||
@withAsyncErrorHandler
|
||||
async fetchPermissions() {
|
||||
const result = await checkPermissions({
|
||||
createCustomForm: {
|
||||
entityTypeId: 'namespace',
|
||||
requiredOperations: ['createCustomForm']
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({
|
||||
createPermitted: result.data.createCustomForm
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.fetchPermissions();
|
||||
static propTypes = {
|
||||
permissions: PropTypes.object
|
||||
}
|
||||
|
||||
render() {
|
||||
const t = this.props.t;
|
||||
|
||||
const permissions = this.props.permissions;
|
||||
const createPermitted = permissions.createCustomForm;
|
||||
|
||||
const columns = [
|
||||
{ data: 1, title: t('name') },
|
||||
{ data: 2, title: t('description') },
|
||||
|
@ -78,7 +66,7 @@ export default class List extends Component {
|
|||
return (
|
||||
<div>
|
||||
{tableRestActionDialogRender(this)}
|
||||
{this.state.createPermitted &&
|
||||
{createPermitted &&
|
||||
<Toolbar>
|
||||
<LinkButton to="/lists/forms/create" className="btn-primary" icon="plus" label={t('createCustomForm')}/>
|
||||
</Toolbar>
|
||||
|
|
|
@ -19,13 +19,29 @@ import ImportRunsStatus from './imports/RunStatus';
|
|||
import Share from '../shares/Share';
|
||||
import TriggersList from './TriggersList';
|
||||
import {ellipsizeBreadcrumbLabel} from "../lib/helpers";
|
||||
import {namespaceCheckPermissions} from "../lib/namespace";
|
||||
|
||||
function getMenus(t) {
|
||||
return {
|
||||
'lists': {
|
||||
title: t('lists'),
|
||||
link: '/lists',
|
||||
panelComponent: ListsList,
|
||||
checkPermissions: {
|
||||
createList: {
|
||||
entityTypeId: 'namespace',
|
||||
requiredOperations: ['createList']
|
||||
},
|
||||
createCustomForm: {
|
||||
entityTypeId: 'namespace',
|
||||
requiredOperations: ['createCustomForm']
|
||||
},
|
||||
viewCustomForm: {
|
||||
entityTypeId: 'customForm',
|
||||
requiredOperations: ['view']
|
||||
},
|
||||
...namespaceCheckPermissions('createList')
|
||||
},
|
||||
panelRender: props => <ListsList permissions={props.permissions}/>,
|
||||
children: {
|
||||
':listId([0-9]+)': {
|
||||
title: resolved => t('listName', {name: ellipsizeBreadcrumbLabel(resolved.list.name)}),
|
||||
|
@ -70,7 +86,7 @@ function getMenus(t) {
|
|||
title: t('edit'),
|
||||
link: params => `/lists/${params.listId}/edit`,
|
||||
visible: resolved => resolved.list.permissions.includes('edit'),
|
||||
panelRender: props => <ListsCUD action={props.match.params.action} entity={props.resolved.list} />
|
||||
panelRender: props => <ListsCUD action={props.match.params.action} entity={props.resolved.list} permissions={props.permissions} />
|
||||
},
|
||||
fields: {
|
||||
title: t('fields'),
|
||||
|
@ -191,12 +207,15 @@ function getMenus(t) {
|
|||
},
|
||||
create: {
|
||||
title: t('create'),
|
||||
panelRender: props => <ListsCUD action="create" />
|
||||
panelRender: props => <ListsCUD action="create" permissions={props.permissions} />
|
||||
},
|
||||
forms: {
|
||||
title: t('customForms-1'),
|
||||
link: '/lists/forms',
|
||||
panelComponent: FormsList,
|
||||
checkPermissions: {
|
||||
...namespaceCheckPermissions('createCustomForm')
|
||||
},
|
||||
panelRender: props => <FormsList permissions={props.permissions}/>,
|
||||
children: {
|
||||
':formsId([0-9]+)': {
|
||||
title: resolved => t('customFormsName', {name: ellipsizeBreadcrumbLabel(resolved.forms.name)}),
|
||||
|
@ -209,7 +228,7 @@ function getMenus(t) {
|
|||
title: t('edit'),
|
||||
link: params => `/lists/forms/${params.formsId}/edit`,
|
||||
visible: resolved => resolved.forms.permissions.includes('edit'),
|
||||
panelRender: props => <FormsCUD action={props.match.params.action} entity={props.resolved.forms} />
|
||||
panelRender: props => <FormsCUD action={props.match.params.action} entity={props.resolved.forms} permissions={props.permissions} />
|
||||
},
|
||||
share: {
|
||||
title: t('share'),
|
||||
|
@ -221,7 +240,7 @@ function getMenus(t) {
|
|||
},
|
||||
create: {
|
||||
title: t('create'),
|
||||
panelRender: props => <FormsCUD action="create" />
|
||||
panelRender: props => <FormsCUD action="create" permissions={props.permissions} />
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import mailtrainConfig from 'mailtrainConfig';
|
|||
import {getGlobalNamespaceId} from "../../../shared/namespaces";
|
||||
import {getUrl} from "../lib/urls";
|
||||
import {withComponentMixins} from "../lib/decorator-helpers";
|
||||
import {getDefaultNamespace} from "../lib/namespace";
|
||||
|
||||
@withComponentMixins([
|
||||
withTranslation,
|
||||
|
@ -43,7 +44,8 @@ export default class CUD extends Component {
|
|||
|
||||
static propTypes = {
|
||||
action: PropTypes.string.isRequired,
|
||||
entity: PropTypes.object
|
||||
entity: PropTypes.object,
|
||||
permissions: PropTypes.object
|
||||
}
|
||||
|
||||
submitFormValuesMutator(data) {
|
||||
|
@ -97,7 +99,7 @@ export default class CUD extends Component {
|
|||
this.populateFormValues({
|
||||
name: '',
|
||||
description: '',
|
||||
namespace: mailtrainConfig.user.namespace
|
||||
namespace: getDefaultNamespace(this.props.permissions)
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -4,13 +4,13 @@ import React, {Component} from 'react';
|
|||
import {withTranslation} from '../lib/i18n';
|
||||
import {LinkButton, requiresAuthenticatedUser, Title, Toolbar, withPageHelpers} from '../lib/page';
|
||||
import {TreeTable} from '../lib/tree';
|
||||
import {withAsyncErrorHandler, withErrorHandling} from '../lib/error-handling';
|
||||
import {withErrorHandling} from '../lib/error-handling';
|
||||
import {Icon} from "../lib/bootstrap-components";
|
||||
import {checkPermissions} from "../lib/permissions";
|
||||
import {tableAddDeleteButton, tableRestActionDialogInit, tableRestActionDialogRender} from "../lib/modals";
|
||||
import {getGlobalNamespaceId} from "../../../shared/namespaces";
|
||||
import {withComponentMixins} from "../lib/decorator-helpers";
|
||||
import mailtrainConfig from 'mailtrainConfig';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
@withComponentMixins([
|
||||
withTranslation,
|
||||
|
@ -26,28 +26,16 @@ export default class List extends Component {
|
|||
tableRestActionDialogInit(this);
|
||||
}
|
||||
|
||||
@withAsyncErrorHandler
|
||||
async fetchPermissions() {
|
||||
const result = await checkPermissions({
|
||||
createNamespace: {
|
||||
entityTypeId: 'namespace',
|
||||
requiredOperations: ['createNamespace']
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({
|
||||
createPermitted: result.data.createNamespace
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.fetchPermissions();
|
||||
static propTypes = {
|
||||
permissions: PropTypes.object
|
||||
}
|
||||
|
||||
render() {
|
||||
const t = this.props.t;
|
||||
|
||||
const permissions = this.props.permissions;
|
||||
const createPermitted = permissions.createNamespace;
|
||||
|
||||
const actions = node => {
|
||||
const actions = [];
|
||||
|
||||
|
@ -76,7 +64,7 @@ export default class List extends Component {
|
|||
return (
|
||||
<div>
|
||||
{tableRestActionDialogRender(this)}
|
||||
{this.state.createPermitted &&
|
||||
{createPermitted &&
|
||||
<Toolbar>
|
||||
<LinkButton to="/namespaces/create" className="btn-primary" icon="plus" label={t('createNamespace')}/>
|
||||
</Toolbar>
|
||||
|
|
|
@ -5,13 +5,21 @@ import CUD from './CUD';
|
|||
import List from './List';
|
||||
import Share from '../shares/Share';
|
||||
import {ellipsizeBreadcrumbLabel} from "../lib/helpers";
|
||||
import {namespaceCheckPermissions} from "../lib/namespace";
|
||||
|
||||
function getMenus(t) {
|
||||
return {
|
||||
namespaces: {
|
||||
title: t('namespaces'),
|
||||
link: '/namespaces',
|
||||
panelComponent: List,
|
||||
checkPermissions: {
|
||||
createNamespace: {
|
||||
entityTypeId: 'namespace',
|
||||
requiredOperations: ['createNamespace']
|
||||
},
|
||||
...namespaceCheckPermissions('createNamespace')
|
||||
},
|
||||
panelRender: props => <List permissions={props.permissions}/>,
|
||||
children: {
|
||||
':namespaceId([0-9]+)': {
|
||||
title: resolved => t('namespaceName', {name: ellipsizeBreadcrumbLabel(resolved.namespace.name)}),
|
||||
|
@ -24,7 +32,7 @@ function getMenus(t) {
|
|||
title: t('edit'),
|
||||
link: params => `/namespaces/${params.namespaceId}/edit`,
|
||||
visible: resolved => resolved.namespace.permissions.includes('edit'),
|
||||
panelRender: props => <CUD action={props.match.params.action} entity={props.resolved.namespace} />
|
||||
panelRender: props => <CUD action={props.match.params.action} entity={props.resolved.namespace} permissions={props.permissions} />
|
||||
},
|
||||
share: {
|
||||
title: t('share'),
|
||||
|
@ -36,7 +44,7 @@ function getMenus(t) {
|
|||
},
|
||||
create: {
|
||||
title: t('create'),
|
||||
panelRender: props => <CUD action="create" />
|
||||
panelRender: props => <CUD action="create" permissions={props.permissions} />
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,9 +21,8 @@ import {
|
|||
import axios from '../lib/axios';
|
||||
import {withAsyncErrorHandler, withErrorHandling} from '../lib/error-handling';
|
||||
import moment from 'moment';
|
||||
import {NamespaceSelect, validateNamespace} from '../lib/namespace';
|
||||
import {getDefaultNamespace, NamespaceSelect, validateNamespace} from '../lib/namespace';
|
||||
import {DeleteModalDialog} from "../lib/modals";
|
||||
import mailtrainConfig from 'mailtrainConfig';
|
||||
import {getUrl} from "../lib/urls";
|
||||
import {withComponentMixins} from "../lib/decorator-helpers";
|
||||
|
||||
|
@ -49,7 +48,8 @@ export default class CUD extends Component {
|
|||
|
||||
static propTypes = {
|
||||
action: PropTypes.string.isRequired,
|
||||
entity: PropTypes.object
|
||||
entity: PropTypes.object,
|
||||
permissions: PropTypes.object
|
||||
}
|
||||
|
||||
@withAsyncErrorHandler
|
||||
|
@ -97,7 +97,7 @@ export default class CUD extends Component {
|
|||
name: '',
|
||||
description: '',
|
||||
report_template: null,
|
||||
namespace: mailtrainConfig.user.namespace,
|
||||
namespace: getDefaultNamespace(this.props.permissions),
|
||||
user_fields: null
|
||||
});
|
||||
}
|
||||
|
|
|
@ -9,10 +9,10 @@ import moment from 'moment';
|
|||
import axios from '../lib/axios';
|
||||
import {ReportState} from '../../../shared/reports';
|
||||
import {Icon} from "../lib/bootstrap-components";
|
||||
import {checkPermissions} from "../lib/permissions";
|
||||
import {getUrl} from "../lib/urls";
|
||||
import {tableAddDeleteButton, tableRestActionDialogInit, tableRestActionDialogRender} from "../lib/modals";
|
||||
import {withComponentMixins} from "../lib/decorator-helpers";
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
@withComponentMixins([
|
||||
withTranslation,
|
||||
|
@ -28,36 +28,8 @@ export default class List extends Component {
|
|||
tableRestActionDialogInit(this);
|
||||
}
|
||||
|
||||
@withAsyncErrorHandler
|
||||
async fetchPermissions() {
|
||||
const result = await checkPermissions({
|
||||
createReport: {
|
||||
entityTypeId: 'namespace',
|
||||
requiredOperations: ['createReport']
|
||||
},
|
||||
executeReportTemplate: {
|
||||
entityTypeId: 'reportTemplate',
|
||||
requiredOperations: ['execute']
|
||||
},
|
||||
createReportTemplate: {
|
||||
entityTypeId: 'namespace',
|
||||
requiredOperations: ['createReportTemplate']
|
||||
},
|
||||
viewReportTemplate: {
|
||||
entityTypeId: 'reportTemplate',
|
||||
requiredOperations: ['view']
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({
|
||||
createPermitted: result.data.createReport && result.data.executeReportTemplate,
|
||||
templatesPermitted: result.data.createReportTemplate || result.data.viewReportTemplate
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.fetchPermissions();
|
||||
static propTypes = {
|
||||
permissions: PropTypes.object
|
||||
}
|
||||
|
||||
@withAsyncErrorHandler
|
||||
|
@ -75,6 +47,10 @@ export default class List extends Component {
|
|||
render() {
|
||||
const t = this.props.t;
|
||||
|
||||
const permissions = this.props.permissions;
|
||||
const createPermitted = permissions.createReport && permissions.executeReportTemplate;
|
||||
const templatesPermitted = permissions.createReportTemplate || permissions.viewReportTemplate;
|
||||
|
||||
const columns = [
|
||||
{ data: 1, title: t('name') },
|
||||
{ data: 2, title: t('template') },
|
||||
|
@ -176,10 +152,10 @@ export default class List extends Component {
|
|||
<div>
|
||||
{tableRestActionDialogRender(this)}
|
||||
<Toolbar>
|
||||
{this.state.createPermitted &&
|
||||
{createPermitted &&
|
||||
<LinkButton to="/reports/create" className="btn-primary" icon="plus" label={t('createReport')}/>
|
||||
}
|
||||
{this.state.templatesPermitted &&
|
||||
{templatesPermitted &&
|
||||
<LinkButton to="/reports/templates" className="btn-primary" label={t('reportTemplates')}/>
|
||||
}
|
||||
</Toolbar>
|
||||
|
|
|
@ -10,6 +10,7 @@ import Share from '../shares/Share';
|
|||
import {ReportState} from '../../../shared/reports';
|
||||
import mailtrainConfig from 'mailtrainConfig';
|
||||
import {ellipsizeBreadcrumbLabel} from "../lib/helpers";
|
||||
import {namespaceCheckPermissions} from "../lib/namespace";
|
||||
|
||||
|
||||
function getMenus(t) {
|
||||
|
@ -17,7 +18,26 @@ function getMenus(t) {
|
|||
'reports': {
|
||||
title: t('reports'),
|
||||
link: '/reports',
|
||||
panelComponent: ReportsList,
|
||||
checkPermissions: {
|
||||
createReport: {
|
||||
entityTypeId: 'namespace',
|
||||
requiredOperations: ['createReport']
|
||||
},
|
||||
executeReportTemplate: {
|
||||
entityTypeId: 'reportTemplate',
|
||||
requiredOperations: ['execute']
|
||||
},
|
||||
createReportTemplate: {
|
||||
entityTypeId: 'namespace',
|
||||
requiredOperations: ['createReportTemplate']
|
||||
},
|
||||
viewReportTemplate: {
|
||||
entityTypeId: 'reportTemplate',
|
||||
requiredOperations: ['view']
|
||||
},
|
||||
...namespaceCheckPermissions('createReport')
|
||||
},
|
||||
panelRender: props => <ReportsList permissions={props.permissions}/>,
|
||||
children: {
|
||||
':reportId([0-9]+)': {
|
||||
title: resolved => t('reportName', {name: ellipsizeBreadcrumbLabel(resolved.report.name)}),
|
||||
|
@ -30,7 +50,7 @@ function getMenus(t) {
|
|||
title: t('edit'),
|
||||
link: params => `/reports/${params.reportId}/edit`,
|
||||
visible: resolved => resolved.report.permissions.includes('edit'),
|
||||
panelRender: props => <ReportsCUD action={props.match.params.action} entity={props.resolved.report} />
|
||||
panelRender: props => <ReportsCUD action={props.match.params.action} entity={props.resolved.report} permissions={props.permissions} />
|
||||
},
|
||||
view: {
|
||||
title: t('view'),
|
||||
|
@ -59,12 +79,15 @@ function getMenus(t) {
|
|||
},
|
||||
create: {
|
||||
title: t('create'),
|
||||
panelRender: props => <ReportsCUD action="create" />
|
||||
panelRender: props => <ReportsCUD action="create" permissions={props.permissions} />
|
||||
},
|
||||
templates: {
|
||||
title: t('templates'),
|
||||
link: '/reports/templates',
|
||||
panelComponent: ReportTemplatesList,
|
||||
checkPermissions: {
|
||||
...namespaceCheckPermissions('createReportTemplate')
|
||||
},
|
||||
panelRender: props => <ReportTemplatesList permissions={props.permissions}/>,
|
||||
children: {
|
||||
':templateId([0-9]+)': {
|
||||
title: resolved => t('templateName', {name: ellipsizeBreadcrumbLabel(resolved.template.name)}),
|
||||
|
@ -77,7 +100,7 @@ function getMenus(t) {
|
|||
title: t('edit'),
|
||||
link: params => `/reports/templates/${params.templateId}/edit`,
|
||||
visible: resolved => mailtrainConfig.globalPermissions.createJavascriptWithROAccess && resolved.template.permissions.includes('edit'),
|
||||
panelRender: props => <ReportTemplatesCUD action={props.match.params.action} entity={props.resolved.template} />
|
||||
panelRender: props => <ReportTemplatesCUD action={props.match.params.action} entity={props.resolved.template} permissions={props.permissions} />
|
||||
},
|
||||
share: {
|
||||
title: t('share'),
|
||||
|
@ -90,7 +113,7 @@ function getMenus(t) {
|
|||
create: {
|
||||
title: t('create'),
|
||||
extraParams: [':wizard?'],
|
||||
panelRender: props => <ReportTemplatesCUD action="create" wizard={props.match.params.wizard} />
|
||||
panelRender: props => <ReportTemplatesCUD action="create" wizard={props.match.params.wizard} permissions={props.permissions} />
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,9 +19,8 @@ import {
|
|||
withFormErrorHandlers
|
||||
} from '../../lib/form';
|
||||
import {withErrorHandling} from '../../lib/error-handling';
|
||||
import {NamespaceSelect, validateNamespace} from '../../lib/namespace';
|
||||
import {getDefaultNamespace, NamespaceSelect, validateNamespace} from '../../lib/namespace';
|
||||
import {DeleteModalDialog} from "../../lib/modals";
|
||||
import mailtrainConfig from 'mailtrainConfig';
|
||||
import 'brace/mode/javascript';
|
||||
import 'brace/mode/json';
|
||||
import 'brace/mode/handlebars';
|
||||
|
@ -46,7 +45,8 @@ export default class CUD extends Component {
|
|||
static propTypes = {
|
||||
action: PropTypes.string.isRequired,
|
||||
wizard: PropTypes.string,
|
||||
entity: PropTypes.object
|
||||
entity: PropTypes.object,
|
||||
permissions: PropTypes.object
|
||||
}
|
||||
|
||||
submitFormValuesMutator(data) {
|
||||
|
@ -64,7 +64,7 @@ export default class CUD extends Component {
|
|||
this.populateFormValues({
|
||||
name: '',
|
||||
description: 'Generates a campaign report listing all subscribers along with open counts.',
|
||||
namespace: mailtrainConfig.user.namespace,
|
||||
namespace: getDefaultNamespace(this.props.permissions),
|
||||
mime_type: 'text/html',
|
||||
user_fields:
|
||||
'[\n' +
|
||||
|
@ -114,7 +114,7 @@ export default class CUD extends Component {
|
|||
this.populateFormValues({
|
||||
name: '',
|
||||
description: 'Generates a campaign report as CSV that lists all subscribers along with open counts.',
|
||||
namespace: mailtrainConfig.user.namespace,
|
||||
namespace: getDefaultNamespace(this.props.permissions),
|
||||
mime_type: 'text/csv',
|
||||
user_fields:
|
||||
'[\n' +
|
||||
|
@ -144,7 +144,7 @@ export default class CUD extends Component {
|
|||
this.populateFormValues({
|
||||
name: '',
|
||||
description: 'Generates a campaign report with results are aggregated by "Country" custom field. (Note that this custom field has to be presents in the subscription custom fields.)',
|
||||
namespace: mailtrainConfig.user.namespace,
|
||||
namespace: getDefaultNamespace(this.props.permissions),
|
||||
mime_type: 'text/html',
|
||||
user_fields:
|
||||
'[\n' +
|
||||
|
@ -215,7 +215,7 @@ export default class CUD extends Component {
|
|||
this.populateFormValues({
|
||||
name: '',
|
||||
description: '',
|
||||
namespace: mailtrainConfig.user.namespace,
|
||||
namespace: getDefaultNamespace(this.props.permissions),
|
||||
mime_type: 'text/html',
|
||||
user_fields: '',
|
||||
js: '',
|
||||
|
|
|
@ -4,13 +4,13 @@ import React, {Component} from 'react';
|
|||
import {withTranslation} from '../../lib/i18n';
|
||||
import {ButtonDropdown, Icon} from '../../lib/bootstrap-components';
|
||||
import {DropdownLink, requiresAuthenticatedUser, Title, Toolbar, withPageHelpers} from '../../lib/page';
|
||||
import {withAsyncErrorHandler, withErrorHandling} from '../../lib/error-handling';
|
||||
import {withErrorHandling} from '../../lib/error-handling';
|
||||
import {Table} from '../../lib/table';
|
||||
import moment from 'moment';
|
||||
import mailtrainConfig from 'mailtrainConfig';
|
||||
import {checkPermissions} from "../../lib/permissions";
|
||||
import {tableAddDeleteButton, tableRestActionDialogInit, tableRestActionDialogRender} from "../../lib/modals";
|
||||
import {withComponentMixins} from "../../lib/decorator-helpers";
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
@withComponentMixins([
|
||||
withTranslation,
|
||||
|
@ -26,28 +26,16 @@ export default class List extends Component {
|
|||
tableRestActionDialogInit(this);
|
||||
}
|
||||
|
||||
@withAsyncErrorHandler
|
||||
async fetchPermissions() {
|
||||
const result = await checkPermissions({
|
||||
createReportTemplate: {
|
||||
entityTypeId: 'namespace',
|
||||
requiredOperations: ['createReportTemplate']
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({
|
||||
createPermitted: result.data.createReportTemplate && mailtrainConfig.globalPermissions.createJavascriptWithROAccess
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.fetchPermissions();
|
||||
static propTypes = {
|
||||
permissions: PropTypes.object
|
||||
}
|
||||
|
||||
render() {
|
||||
const t = this.props.t;
|
||||
|
||||
const permissions = this.props.permissions;
|
||||
const createPermitted = permissions.createReportTemplate && mailtrainConfig.globalPermissions.createJavascriptWithROAccess;
|
||||
|
||||
const columns = [
|
||||
{ data: 1, title: t('name') },
|
||||
{ data: 2, title: t('description') },
|
||||
|
@ -82,7 +70,7 @@ export default class List extends Component {
|
|||
return (
|
||||
<div>
|
||||
{tableRestActionDialogRender(this)}
|
||||
{this.state.createPermitted &&
|
||||
{createPermitted &&
|
||||
<Toolbar>
|
||||
<ButtonDropdown buttonClassName="btn-primary" menuClassName="dropdown-menu-right" label={t('createReportTemplate')}>
|
||||
<DropdownLink to="/reports/templates/create">{t('blank')}</DropdownLink>
|
||||
|
|
|
@ -20,7 +20,7 @@ import {
|
|||
withFormErrorHandlers
|
||||
} from '../lib/form';
|
||||
import {withErrorHandling} from '../lib/error-handling';
|
||||
import {NamespaceSelect, validateNamespace} from '../lib/namespace';
|
||||
import {getDefaultNamespace, NamespaceSelect, validateNamespace} from '../lib/namespace';
|
||||
import {DeleteModalDialog} from "../lib/modals";
|
||||
|
||||
import {getMailerTypes} from "./helpers";
|
||||
|
@ -60,7 +60,8 @@ export default class CUD extends Component {
|
|||
static propTypes = {
|
||||
action: PropTypes.string.isRequired,
|
||||
wizard: PropTypes.string,
|
||||
entity: PropTypes.object
|
||||
entity: PropTypes.object,
|
||||
permissions: PropTypes.object
|
||||
}
|
||||
|
||||
onMailerTypeChanged(mutStateDate, key, oldType, type) {
|
||||
|
@ -96,7 +97,7 @@ export default class CUD extends Component {
|
|||
this.populateFormValues({
|
||||
name: '',
|
||||
description: '',
|
||||
namespace: mailtrainConfig.user.namespace,
|
||||
namespace: getDefaultNamespace(this.props.permissions),
|
||||
from_email: '',
|
||||
from_email_overridable: false,
|
||||
from_name: '',
|
||||
|
|
|
@ -4,13 +4,13 @@ import React, {Component} from 'react';
|
|||
import {withTranslation} from '../lib/i18n';
|
||||
import {Icon} from '../lib/bootstrap-components';
|
||||
import {LinkButton, requiresAuthenticatedUser, Title, Toolbar, withPageHelpers} from '../lib/page';
|
||||
import {withAsyncErrorHandler, withErrorHandling} from '../lib/error-handling';
|
||||
import {withErrorHandling} from '../lib/error-handling';
|
||||
import {Table} from '../lib/table';
|
||||
import moment from 'moment';
|
||||
import {getMailerTypes} from './helpers';
|
||||
import {checkPermissions} from "../lib/permissions";
|
||||
import {tableAddDeleteButton, tableRestActionDialogInit, tableRestActionDialogRender} from "../lib/modals";
|
||||
import {withComponentMixins} from "../lib/decorator-helpers";
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
|
||||
@withComponentMixins([
|
||||
|
@ -29,28 +29,16 @@ export default class List extends Component {
|
|||
tableRestActionDialogInit(this);
|
||||
}
|
||||
|
||||
@withAsyncErrorHandler
|
||||
async fetchPermissions() {
|
||||
const result = await checkPermissions({
|
||||
createSendConfiguration: {
|
||||
entityTypeId: 'namespace',
|
||||
requiredOperations: ['createSendConfiguration']
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({
|
||||
createPermitted: result.data.createSendConfiguration
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.fetchPermissions();
|
||||
static propTypes = {
|
||||
permissions: PropTypes.object
|
||||
}
|
||||
|
||||
render() {
|
||||
const t = this.props.t;
|
||||
|
||||
const permissions = this.props.permissions;
|
||||
const createPermitted = permissions.createSendConfiguration;
|
||||
|
||||
const columns = [
|
||||
{ data: 1, title: t('name') },
|
||||
{ data: 2, title: t('id'), render: data => <code>{data}</code> },
|
||||
|
@ -87,7 +75,7 @@ export default class List extends Component {
|
|||
return (
|
||||
<div>
|
||||
{tableRestActionDialogRender(this)}
|
||||
{this.state.createPermitted &&
|
||||
{createPermitted &&
|
||||
<Toolbar>
|
||||
<LinkButton to="/send-configurations/create" className="btn-primary" icon="plus" label={t('createSendConfiguration')}/>
|
||||
</Toolbar>
|
||||
|
|
|
@ -6,6 +6,7 @@ import CUD from './CUD';
|
|||
import List from './List';
|
||||
import Share from '../shares/Share';
|
||||
import {ellipsizeBreadcrumbLabel} from "../lib/helpers";
|
||||
import {namespaceCheckPermissions} from "../lib/namespace";
|
||||
|
||||
|
||||
function getMenus(t) {
|
||||
|
@ -13,7 +14,14 @@ function getMenus(t) {
|
|||
'send-configurations': {
|
||||
title: t('sendConfigurations-1'),
|
||||
link: '/send-configurations',
|
||||
panelComponent: List,
|
||||
checkPermissions: {
|
||||
createSendConfiguration: {
|
||||
entityTypeId: 'namespace',
|
||||
requiredOperations: ['createSendConfiguration']
|
||||
},
|
||||
...namespaceCheckPermissions('createSendConfiguration')
|
||||
},
|
||||
panelRender: props => <List permissions={props.permissions}/>,
|
||||
children: {
|
||||
':sendConfigurationId([0-9]+)': {
|
||||
title: resolved => t('templateName', {name: ellipsizeBreadcrumbLabel(resolved.sendConfiguration.name)}),
|
||||
|
@ -26,7 +34,7 @@ function getMenus(t) {
|
|||
title: t('edit'),
|
||||
link: params => `/send-configurations/${params.sendConfigurationId}/edit`,
|
||||
visible: resolved => resolved.sendConfiguration.permissions.includes('edit'),
|
||||
panelRender: props => <CUD action={props.match.params.action} entity={props.resolved.sendConfiguration} />
|
||||
panelRender: props => <CUD action={props.match.params.action} entity={props.resolved.sendConfiguration} permissions={props.permissions} />
|
||||
},
|
||||
share: {
|
||||
title: t('share'),
|
||||
|
@ -38,7 +46,7 @@ function getMenus(t) {
|
|||
},
|
||||
create: {
|
||||
title: t('create'),
|
||||
panelRender: props => <CUD action="create" />
|
||||
panelRender: props => <CUD action="create" permissions={props.permissions} />
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -139,7 +139,6 @@ export default class Share extends Component {
|
|||
|
||||
let usersLabelIndex = 1;
|
||||
const usersColumns = [
|
||||
{ data: 0, title: "#" },
|
||||
{ data: 1, title: "Username" },
|
||||
];
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ import {
|
|||
withFormErrorHandlers
|
||||
} from '../lib/form';
|
||||
import {withErrorHandling} from '../lib/error-handling';
|
||||
import {NamespaceSelect, validateNamespace} from '../lib/namespace';
|
||||
import {getDefaultNamespace, NamespaceSelect, validateNamespace} from '../lib/namespace';
|
||||
import {ContentModalDialog, DeleteModalDialog} from "../lib/modals";
|
||||
import mailtrainConfig from 'mailtrainConfig';
|
||||
import {getEditForm, getTagLanguages, getTemplateTypes, getTypeForm} from './helpers';
|
||||
|
@ -75,6 +75,7 @@ export default class CUD extends Component {
|
|||
action: PropTypes.string.isRequired,
|
||||
wizard: PropTypes.string,
|
||||
entity: PropTypes.object,
|
||||
permissions: PropTypes.object,
|
||||
setPanelInFullScreen: PropTypes.func
|
||||
}
|
||||
|
||||
|
@ -124,7 +125,7 @@ export default class CUD extends Component {
|
|||
this.populateFormValues({
|
||||
name: '',
|
||||
description: '',
|
||||
namespace: mailtrainConfig.user.namespace,
|
||||
namespace: getDefaultNamespace(this.props.permissions),
|
||||
type: mailtrainConfig.editors[0],
|
||||
tag_language: mailtrainConfig.tagLanguages[0],
|
||||
|
||||
|
|
|
@ -4,13 +4,13 @@ import React, {Component} from 'react';
|
|||
import {withTranslation} from '../lib/i18n';
|
||||
import {Icon} from '../lib/bootstrap-components';
|
||||
import {LinkButton, requiresAuthenticatedUser, Title, Toolbar, withPageHelpers} from '../lib/page';
|
||||
import {withAsyncErrorHandler, withErrorHandling} from '../lib/error-handling';
|
||||
import {withErrorHandling} from '../lib/error-handling';
|
||||
import {Table} from '../lib/table';
|
||||
import moment from 'moment';
|
||||
import {getTemplateTypes, getTagLanguages} from './helpers';
|
||||
import {checkPermissions} from "../lib/permissions";
|
||||
import {getTagLanguages, getTemplateTypes} from './helpers';
|
||||
import {tableAddDeleteButton, tableRestActionDialogInit, tableRestActionDialogRender} from "../lib/modals";
|
||||
import {withComponentMixins} from "../lib/decorator-helpers";
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
|
||||
@withComponentMixins([
|
||||
|
@ -30,37 +30,17 @@ export default class List extends Component {
|
|||
tableRestActionDialogInit(this);
|
||||
}
|
||||
|
||||
@withAsyncErrorHandler
|
||||
async fetchPermissions() {
|
||||
const result = await checkPermissions({
|
||||
createTemplate: {
|
||||
entityTypeId: 'namespace',
|
||||
requiredOperations: ['createTemplate']
|
||||
},
|
||||
createMosaicoTemplate: {
|
||||
entityTypeId: 'namespace',
|
||||
requiredOperations: ['createMosaicoTemplate']
|
||||
},
|
||||
viewMosaicoTemplate: {
|
||||
entityTypeId: 'mosaicoTemplate',
|
||||
requiredOperations: ['view']
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({
|
||||
createPermitted: result.data.createTemplate,
|
||||
mosaicoTemplatesPermitted: result.data.createMosaicoTemplate || result.data.viewMosaicoTemplate
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.fetchPermissions();
|
||||
static propTypes = {
|
||||
permissions: PropTypes.object
|
||||
}
|
||||
|
||||
render() {
|
||||
const t = this.props.t;
|
||||
|
||||
const permissions = this.props.permissions;
|
||||
const createPermitted = permissions.createTemplate;
|
||||
const mosaicoTemplatesPermitted = permissions.createMosaicoTemplate || permissions.viewMosaicoTemplate;
|
||||
|
||||
const columns = [
|
||||
{ data: 1, title: t('name') },
|
||||
{ data: 2, title: t('description') },
|
||||
|
@ -105,10 +85,10 @@ export default class List extends Component {
|
|||
<div>
|
||||
{tableRestActionDialogRender(this)}
|
||||
<Toolbar>
|
||||
{this.state.createPermitted &&
|
||||
{createPermitted &&
|
||||
<LinkButton to="/templates/create" className="btn-primary" icon="plus" label={t('createTemplate')}/>
|
||||
}
|
||||
{this.state.mosaicoTemplatesPermitted &&
|
||||
{mosaicoTemplatesPermitted &&
|
||||
<LinkButton to="/templates/mosaico" className="btn-primary" label={t('mosaicoTemplates')}/>
|
||||
}
|
||||
</Toolbar>
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
withFormErrorHandlers
|
||||
} from '../../lib/form';
|
||||
import {withErrorHandling} from '../../lib/error-handling';
|
||||
import {NamespaceSelect, validateNamespace} from '../../lib/namespace';
|
||||
import {getDefaultNamespace, NamespaceSelect, validateNamespace} from '../../lib/namespace';
|
||||
import {DeleteModalDialog} from "../../lib/modals";
|
||||
import mailtrainConfig from 'mailtrainConfig';
|
||||
import {getMJMLSample, getVersafix} from "../../../../shared/mosaico-templates";
|
||||
|
@ -57,7 +57,8 @@ export default class CUD extends Component {
|
|||
static propTypes = {
|
||||
action: PropTypes.string.isRequired,
|
||||
wizard: PropTypes.string,
|
||||
entity: PropTypes.object
|
||||
entity: PropTypes.object,
|
||||
permissions: PropTypes.object
|
||||
}
|
||||
|
||||
getFormValuesMutator(data) {
|
||||
|
@ -88,7 +89,7 @@ export default class CUD extends Component {
|
|||
this.populateFormValues({
|
||||
name: '',
|
||||
description: '',
|
||||
namespace: mailtrainConfig.user.namespace,
|
||||
namespace: getDefaultNamespace(this.props.permissions),
|
||||
type: 'html',
|
||||
tag_language: mailtrainConfig.tagLanguages[0]
|
||||
});
|
||||
|
@ -97,7 +98,7 @@ export default class CUD extends Component {
|
|||
this.populateFormValues({
|
||||
name: '',
|
||||
description: '',
|
||||
namespace: mailtrainConfig.user.namespace,
|
||||
namespace: getDefaultNamespace(this.props.permissions),
|
||||
type: 'mjml',
|
||||
tag_language: mailtrainConfig.tagLanguages[0]
|
||||
});
|
||||
|
@ -106,7 +107,7 @@ export default class CUD extends Component {
|
|||
this.populateFormValues({
|
||||
name: '',
|
||||
description: '',
|
||||
namespace: mailtrainConfig.user.namespace,
|
||||
namespace: getDefaultNamespace(this.props.permissions),
|
||||
type: 'html',
|
||||
tag_language: mailtrainConfig.tagLanguages[0],
|
||||
html: ''
|
||||
|
|
|
@ -4,14 +4,14 @@ import React, {Component} from 'react';
|
|||
import {withTranslation} from '../../lib/i18n';
|
||||
import {ButtonDropdown, Icon} from '../../lib/bootstrap-components';
|
||||
import {DropdownLink, requiresAuthenticatedUser, Title, Toolbar, withPageHelpers} from '../../lib/page';
|
||||
import {withAsyncErrorHandler, withErrorHandling} from '../../lib/error-handling';
|
||||
import {withErrorHandling} from '../../lib/error-handling';
|
||||
import {Table} from '../../lib/table';
|
||||
import moment from 'moment';
|
||||
import {getTemplateTypes} from './helpers';
|
||||
import {getTagLanguages} from '../helpers';
|
||||
import {checkPermissions} from "../../lib/permissions";
|
||||
import {tableAddDeleteButton, tableRestActionDialogInit, tableRestActionDialogRender} from "../../lib/modals";
|
||||
import {withComponentMixins} from "../../lib/decorator-helpers";
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
|
||||
@withComponentMixins([
|
||||
|
@ -31,28 +31,16 @@ export default class List extends Component {
|
|||
tableRestActionDialogInit(this);
|
||||
}
|
||||
|
||||
@withAsyncErrorHandler
|
||||
async fetchPermissions() {
|
||||
const result = await checkPermissions({
|
||||
createMosaicoTemplate: {
|
||||
entityTypeId: 'namespace',
|
||||
requiredOperations: ['createMosaicoTemplate']
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({
|
||||
createPermitted: result.data.createMosaicoTemplate
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.fetchPermissions();
|
||||
static propTypes = {
|
||||
permissions: PropTypes.object
|
||||
}
|
||||
|
||||
render() {
|
||||
const t = this.props.t;
|
||||
|
||||
const permissions = this.props.permissions;
|
||||
const createPermitted = permissions.createMosaicoTemplate;
|
||||
|
||||
const columns = [
|
||||
{ data: 1, title: t('name') },
|
||||
{ data: 2, title: t('description') },
|
||||
|
@ -103,7 +91,7 @@ export default class List extends Component {
|
|||
return (
|
||||
<div>
|
||||
{tableRestActionDialogRender(this)}
|
||||
{this.state.createPermitted &&
|
||||
{createPermitted &&
|
||||
<Toolbar>
|
||||
<ButtonDropdown buttonClassName="btn-primary" menuClassName="dropdown-menu-right" label={t('createMosaicoTemplate')}>
|
||||
<DropdownLink to="/templates/mosaico/create">{t('blank')}</DropdownLink>
|
||||
|
|
|
@ -9,14 +9,29 @@ import Files from "../lib/files";
|
|||
import MosaicoCUD from './mosaico/CUD';
|
||||
import MosaicoList from './mosaico/List';
|
||||
import {ellipsizeBreadcrumbLabel} from "../lib/helpers";
|
||||
|
||||
import {namespaceCheckPermissions} from "../lib/namespace";
|
||||
|
||||
function getMenus(t) {
|
||||
return {
|
||||
'templates': {
|
||||
title: t('templates'),
|
||||
link: '/templates',
|
||||
panelComponent: TemplatesList,
|
||||
checkPermissions: {
|
||||
createTemplate: {
|
||||
entityTypeId: 'namespace',
|
||||
requiredOperations: ['createTemplate']
|
||||
},
|
||||
createMosaicoTemplate: {
|
||||
entityTypeId: 'namespace',
|
||||
requiredOperations: ['createMosaicoTemplate']
|
||||
},
|
||||
viewMosaicoTemplate: {
|
||||
entityTypeId: 'mosaicoTemplate',
|
||||
requiredOperations: ['view']
|
||||
},
|
||||
...namespaceCheckPermissions('createTemplate')
|
||||
},
|
||||
panelRender: props => <TemplatesList permissions={props.permissions}/>,
|
||||
children: {
|
||||
':templateId([0-9]+)': {
|
||||
title: resolved => t('templateName', {name: ellipsizeBreadcrumbLabel(resolved.template.name)}),
|
||||
|
@ -29,7 +44,7 @@ function getMenus(t) {
|
|||
title: t('edit'),
|
||||
link: params => `/templates/${params.templateId}/edit`,
|
||||
visible: resolved => resolved.template.permissions.includes('edit'),
|
||||
panelRender: props => <TemplatesCUD action={props.match.params.action} entity={props.resolved.template} setPanelInFullScreen={props.setPanelInFullScreen} />
|
||||
panelRender: props => <TemplatesCUD action={props.match.params.action} entity={props.resolved.template} permissions={props.permissions} setPanelInFullScreen={props.setPanelInFullScreen} />
|
||||
},
|
||||
files: {
|
||||
title: t('files'),
|
||||
|
@ -47,12 +62,15 @@ function getMenus(t) {
|
|||
},
|
||||
create: {
|
||||
title: t('create'),
|
||||
panelRender: props => <TemplatesCUD action="create" />
|
||||
panelRender: props => <TemplatesCUD action="create" permissions={props.permissions} />
|
||||
},
|
||||
mosaico: {
|
||||
title: t('mosaicoTemplates'),
|
||||
link: '/templates/mosaico',
|
||||
panelComponent: MosaicoList,
|
||||
checkPermissions: {
|
||||
...namespaceCheckPermissions('createMosaicoTemplate')
|
||||
},
|
||||
panelRender: props => <MosaicoList permissions={props.permissions}/>,
|
||||
children: {
|
||||
':mosaiceTemplateId([0-9]+)': {
|
||||
title: resolved => t('mosaicoTemplateName', {name: ellipsizeBreadcrumbLabel(resolved.mosaicoTemplate.name)}),
|
||||
|
@ -65,7 +83,7 @@ function getMenus(t) {
|
|||
title: t('edit'),
|
||||
link: params => `/templates/mosaico/${params.mosaiceTemplateId}/edit`,
|
||||
visible: resolved => resolved.mosaicoTemplate.permissions.includes('edit'),
|
||||
panelRender: props => <MosaicoCUD action={props.match.params.action} entity={props.resolved.mosaicoTemplate} />
|
||||
panelRender: props => <MosaicoCUD action={props.match.params.action} entity={props.resolved.mosaicoTemplate} permissions={props.permissions}/>
|
||||
},
|
||||
files: {
|
||||
title: t('files'),
|
||||
|
@ -90,7 +108,7 @@ function getMenus(t) {
|
|||
create: {
|
||||
title: t('create'),
|
||||
extraParams: [':wizard?'],
|
||||
panelRender: props => <MosaicoCUD action="create" wizard={props.match.params.wizard} />
|
||||
panelRender: props => <MosaicoCUD action="create" wizard={props.match.params.wizard} permissions={props.permissions} />
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ import {withErrorHandling} from '../lib/error-handling';
|
|||
import interoperableErrors from '../../../shared/interoperable-errors';
|
||||
import passwordValidator from '../../../shared/password-validator';
|
||||
import mailtrainConfig from 'mailtrainConfig';
|
||||
import {NamespaceSelect, validateNamespace} from '../lib/namespace';
|
||||
import {getDefaultNamespace, NamespaceSelect, validateNamespace} from '../lib/namespace';
|
||||
import {DeleteModalDialog} from "../lib/modals";
|
||||
import {withComponentMixins} from "../lib/decorator-helpers";
|
||||
|
||||
|
@ -49,7 +49,8 @@ export default class CUD extends Component {
|
|||
|
||||
static propTypes = {
|
||||
action: PropTypes.string.isRequired,
|
||||
entity: PropTypes.object
|
||||
entity: PropTypes.object,
|
||||
permissions: PropTypes.object
|
||||
}
|
||||
|
||||
getFormValuesMutator(data) {
|
||||
|
@ -71,7 +72,7 @@ export default class CUD extends Component {
|
|||
email: '',
|
||||
password: '',
|
||||
password2: '',
|
||||
namespace: mailtrainConfig.user.namespace,
|
||||
namespace: getDefaultNamespace(this.props.permissions),
|
||||
role: null
|
||||
});
|
||||
}
|
||||
|
|
|
@ -5,12 +5,17 @@ import CUD from './CUD';
|
|||
import List from './List';
|
||||
import UserShares from '../shares/UserShares';
|
||||
import {ellipsizeBreadcrumbLabel} from "../lib/helpers";
|
||||
import {namespaceCheckPermissions} from "../lib/namespace";
|
||||
import MosaicoCUD from "../templates/mosaico/CUD";
|
||||
|
||||
function getMenus(t) {
|
||||
return {
|
||||
'users': {
|
||||
title: t('users'),
|
||||
link: '/users',
|
||||
checkPermissions: {
|
||||
...namespaceCheckPermissions('manageUsers')
|
||||
},
|
||||
panelComponent: List,
|
||||
children: {
|
||||
':userId([0-9]+)': {
|
||||
|
@ -23,7 +28,7 @@ function getMenus(t) {
|
|||
':action(edit|delete)': {
|
||||
title: t('edit'),
|
||||
link: params => `/users/${params.userId}/edit`,
|
||||
panelRender: props => <CUD action={props.match.params.action} entity={props.resolved.user} />
|
||||
panelRender: props => <CUD action={props.match.params.action} entity={props.resolved.user} permissions={props.permissions} />
|
||||
},
|
||||
shares: {
|
||||
title: t('shares'),
|
||||
|
@ -34,7 +39,7 @@ function getMenus(t) {
|
|||
},
|
||||
create: {
|
||||
title: t('create'),
|
||||
panelRender: props => <CUD action="create" />
|
||||
panelRender: props => <CUD action="create" permissions={props.permissions} />
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -257,6 +257,10 @@ seleniumWebDriver:
|
|||
browser: phantomjs
|
||||
|
||||
|
||||
# The section below defines the definition of roles (permissions) to be used when no "roles" section is provided
|
||||
# in custom config (typically production.yaml). If you want to extend rules provided below, add corresponding rules
|
||||
# in "defaultRoles" section in custom config. If you want to define roles from scratch, create "roles" section in
|
||||
# the custom config.
|
||||
defaultRoles:
|
||||
global:
|
||||
master:
|
||||
|
@ -308,12 +312,14 @@ defaultRoles:
|
|||
|
||||
campaignsCreator:
|
||||
name: Campaigns Creator
|
||||
description: In the respective namespace, the user has all permissions to create and manage templates and campaigns.
|
||||
description: In the respective namespace, the user has all permissions to create and manage templates and campaigns. The user can also read public data about send configurations and use Mosaico templates in the namespace.
|
||||
permissions: [view, createTemplate, createCampaign]
|
||||
children:
|
||||
sendConfiguration: [viewPublic]
|
||||
campaign: [view, edit, delete, share, viewFiles, manageFiles, viewAttachments, manageAttachments, viewTriggers, manageTriggers, sendToTestUsers, fetchRss]
|
||||
template: [view, edit, delete, share, viewFiles, manageFiles]
|
||||
mosaicoTemplate: [view, viewFiles]
|
||||
namespace: [view, createTemplate, createCampaign]
|
||||
|
||||
sendConfiguration:
|
||||
master:
|
||||
|
@ -378,5 +384,9 @@ defaultRoles:
|
|||
name: Master
|
||||
description: All permissions
|
||||
permissions: [view, edit, delete, share, viewFiles, manageFiles]
|
||||
campaignsCreator:
|
||||
name: Campaigns Creator
|
||||
description: The user can use the Mosaico template, but cannot edit it or delete it.
|
||||
permissions: [view, viewFiles]
|
||||
|
||||
|
||||
|
|
|
@ -73,7 +73,6 @@ async function create(context, entity) {
|
|||
|
||||
async function updateWithConsistencyCheck(context, entity) {
|
||||
await knex.transaction(async tx => {
|
||||
await shares.enforceGlobalPermission(context, 'createJavascriptWithROAccess');
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'mosaicoTemplate', entity.id, 'edit');
|
||||
|
||||
const existing = await tx('mosaico_templates').where('id', entity.id).first();
|
||||
|
|
|
@ -35,12 +35,13 @@ router.putAsync('/shares', passport.loggedIn, async (req, res) => {
|
|||
Accepts format:
|
||||
{
|
||||
XXX1: {
|
||||
entityTypeId: ...
|
||||
entityTypeId: ...,
|
||||
requiredOperations: [ ... ]
|
||||
},
|
||||
|
||||
XXX2: {
|
||||
entityTypeId: ...
|
||||
entityTypeId: ...,
|
||||
entityId: ...,
|
||||
requiredOperations: [ ... ]
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue