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} />
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue