Namespace Filter

This commit is contained in:
root 2019-08-14 13:02:40 +02:00
parent 6b6fa8b3ef
commit 8d58c3d282
45 changed files with 783 additions and 158 deletions

View file

@ -22,7 +22,7 @@ import {
withFormErrorHandlers
} from '../lib/form';
import {withAsyncErrorHandler, withErrorHandling} from '../lib/error-handling';
import {getDefaultNamespace, NamespaceSelect, validateNamespace} from '../lib/namespace';
import {getDefaultNamespace, NamespaceSelect, validateNamespace, getNamespaceIdFilterCookie} from '../lib/namespace';
import {DeleteModalDialog} from "../lib/modals";
import mailtrainConfig from 'mailtrainConfig';
import {getTagLanguages, getTemplateTypes, getTypeForm, ResourceType} from '../templates/helpers';
@ -529,7 +529,7 @@ export default class CUD extends Component {
const canDelete = isEdit && this.props.entity.permissions.includes('delete');
let extraSettings = null;
const sourceTypeKey = Number.parseInt(this.getFormValue('source'));
const campaignTypeKey = this.getFormValue('type');
@ -557,6 +557,10 @@ export default class CUD extends Component {
const lstOrderIdxClosure = lstOrderIdx;
const selectedList = this.getFormValue(prefix + 'list');
var listsTable = <TableSelect id={prefix + 'list'} label={t('list')} withHeader dropdown dataUrl='rest/lists-table' columns={listsColumns} selectionLabelIndex={1} />;
if(mailtrainConfig.namespaceFilterEnabled && getNamespaceIdFilterCookie()){
listsTable = <TableSelect id={prefix + 'list'} label={t('list')} withHeader dropdown dataUrl={'rest/lists-table/'+ getNamespaceIdFilterCookie()} columns={listsColumns} selectionLabelIndex={1} />;
}
lstsEditEntries.push(
<div key={lstUid} className={campaignsStyles.entry + ' ' + campaignsStyles.entryWithButtons}>
@ -593,7 +597,8 @@ export default class CUD extends Component {
}
</div>
<div className={campaignsStyles.entryContent}>
<TableSelect id={prefix + 'list'} label={t('list')} withHeader dropdown dataUrl='rest/lists-table' columns={listsColumns} selectionLabelIndex={1} />
{listsTable}
{(campaignTypeKey === CampaignType.REGULAR || campaignTypeKey === CampaignType.RSS) &&
<div>
@ -697,6 +702,9 @@ export default class CUD extends Component {
// The "key" property here and in the TableSelect below is to tell React that these tables are different and should be rendered by different instances. Otherwise, React will use
// only one instance, which fails because Table does not handle updates in "columns" property
templateEdit = <TableSelect key="templateSelect" id="data_sourceTemplate" label={t('template')} withHeader dropdown dataUrl='rest/templates-table' columns={templatesColumns} selectionLabelIndex={1} help={help}/>;
if(mailtrainConfig.namespaceFilterEnabled && getNamespaceIdFilterCookie()){
templateEdit = <TableSelect key="templateSelect" id="data_sourceTemplate" label={t('template')} withHeader dropdown dataUrl={'rest/templates-table/'+getNamespaceIdFilterCookie()} columns={templatesColumns} selectionLabelIndex={1} help={help}/>;
}
} else if (!isEdit && sourceTypeKey === CampaignSource.CUSTOM_FROM_CAMPAIGN) {
const campaignsColumns = [
@ -707,9 +715,10 @@ export default class CUD extends Component {
{ data: 5, title: t('created'), render: data => moment(data).fromNow() },
{ data: 6, title: t('namespace') }
];
templateEdit = <TableSelect key="campaignSelect" id="data_sourceCampaign" label={t('campaign')} withHeader dropdown dataUrl='rest/campaigns-with-content-table' columns={campaignsColumns} selectionLabelIndex={1} help={t('contentOfTheSelectedCampaignWillBeCopied')}/>;
if(mailtrainConfig.namespaceFilterEnabled && getNamespaceIdFilterCookie()){
templateEdit = <TableSelect key="campaignSelect" id="data_sourceCampaign" label={t('campaign')} withHeader dropdown dataUrl={'rest/campaigns-with-content-table/' + getNamespaceIdFilterCookie()} columns={campaignsColumns} selectionLabelIndex={1} help={t('contentOfTheSelectedCampaignWillBeCopied')}/>;
}
} else if (!isEdit && sourceTypeKey === CampaignSource.CUSTOM) {
const customTemplateTypeKey = this.getFormValue('data_sourceCustom_type');
@ -730,6 +739,12 @@ export default class CUD extends Component {
templateEdit = <InputField id="data_sourceUrl" label={t('renderUrl')} help={t('ifAMessageIsSentThenThisUrlWillBePosTed')}/>
}
var sendConfigTable = <TableSelect id="send_configuration" label={t('sendConfiguration')} withHeader dropdown dataUrl='rest/send-configurations-table' columns={sendConfigurationsColumns} selectionLabelIndex={1} />;
if(mailtrainConfig.namespaceFilterEnabled && getNamespaceIdFilterCookie()){
sendConfigTable = <TableSelect id="send_configuration" label={t('sendConfiguration')} withHeader dropdown dataUrl={'rest/send-configurations-table/' + getNamespaceIdFilterCookie()} columns={sendConfigurationsColumns} selectionLabelIndex={1} />
}
return (
<div>
{canDelete &&
@ -773,8 +788,8 @@ export default class CUD extends Component {
<hr/>
<Fieldset label={t('sendSettings')}>
<TableSelect id="send_configuration" label={t('sendConfiguration')} withHeader dropdown dataUrl='rest/send-configurations-table' columns={sendConfigurationsColumns} selectionLabelIndex={1} />
{sendConfigTable}
{sendSettings}

View file

@ -11,8 +11,10 @@ import {CampaignSource, CampaignStatus, CampaignType} from "../../../shared/camp
import {getCampaignLabels} from "./helpers";
import {tableAddDeleteButton, tableRestActionDialogInit, tableRestActionDialogRender} from "../lib/modals";
import {withComponentMixins} from "../lib/decorator-helpers";
import {getNamespaceIdFilterCookie} from "../lib/namespace";
import styles from "./styles.scss";
import PropTypes from 'prop-types';
import mailtrainConfig from 'mailtrainConfig';
@withComponentMixins([
withTranslation,
@ -137,6 +139,10 @@ export default class List extends Component {
}
];
var campaingsTable = <Table ref={node => this.table = node} withHeader dataUrl={"rest/campaigns-table"} columns={columns} />;
if(mailtrainConfig.namespaceFilterEnabled && getNamespaceIdFilterCookie()){
campaingsTable = <Table ref={node => this.table = node} withHeader dataUrl={"rest/campaigns-table/" + getNamespaceIdFilterCookie()} columns={columns} />;
}
return (
<div>
{tableRestActionDialogRender(this)}
@ -152,7 +158,7 @@ export default class List extends Component {
<Title>{t('campaigns')}</Title>
<Table ref={node => this.table = node} withHeader dataUrl="rest/campaigns-table" columns={columns} />
{campaingsTable}
</div>
);
}

View file

@ -4,6 +4,7 @@ import React, {Component} from 'react';
import {withTranslation} from './i18n';
import {TreeTableSelect} from './form';
import {withComponentMixins} from "./decorator-helpers";
import mailtrainConfig from 'mailtrainConfig';
@withComponentMixins([
@ -12,7 +13,11 @@ import {withComponentMixins} from "./decorator-helpers";
export class NamespaceSelect extends Component {
render() {
const t = this.props.t;
if(mailtrainConfig.namespaceFilterEnabled && getNamespaceIdFilterCookie()){
return (
<TreeTableSelect id="namespace" label={t('namespace')} dataUrl={"rest/namespaces-tree/" + getNamespaceIdFilterCookie()}/>
);
}
return (
<TreeTableSelect id="namespace" label={t('namespace')} dataUrl="rest/namespaces-tree"/>
);
@ -27,10 +32,34 @@ export function validateNamespace(t, state) {
}
}
function getCookie(cname) {
var name = cname + "=";
var decodedCookie = decodeURIComponent(document.cookie);
var ca = decodedCookie.split(';');
for(var i = 0; i <ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return "";
}
export function getDefaultNamespace(permissions) {
return permissions.viewUsersNamespace && permissions.createEntityInUsersNamespace ? mailtrainConfig.user.namespace : null;
}
export function getNamespaceIdFilterCookie() {
return getCookie("namespaceFilterId");
}
export function getNamespaceNameFilterCookie() {
return getCookie("namespaceFilterName");
}
export function namespaceCheckPermissions(createOperation) {
if (mailtrainConfig.user) {
return {

View file

@ -22,11 +22,12 @@ import {
} from '../lib/form';
import {withErrorHandling} from '../lib/error-handling';
import {DeleteModalDialog} from '../lib/modals';
import {getDefaultNamespace, NamespaceSelect, validateNamespace} from '../lib/namespace';
import {getDefaultNamespace, NamespaceSelect, validateNamespace, getNamespaceIdFilterCookie} from '../lib/namespace';
import {FieldWizard, UnsubscriptionMode} from '../../../shared/lists';
import styles from "../lib/styles.scss";
import {getMailerTypes} from "../send-configurations/helpers";
import {withComponentMixins} from "../lib/decorator-helpers";
import mailtrainConfig from 'mailtrainConfig';
@withComponentMixins([
withTranslation,
@ -239,6 +240,12 @@ export default class CUD extends Component {
}
}
var sendConfigTable = <TableSelect id="send_configuration" label={t('sendConfiguration')} withHeader dropdown dataUrl='rest/send-configurations-table' columns={sendConfigurationsColumns} selectionLabelIndex={1} help={t('sendConfigurationThatWillBeUsedFor')}/>
if(mailtrainConfig.namespaceFilterEnabled && getNamespaceIdFilterCookie()){
sendConfigTable = <TableSelect id="send_configuration" label={t('sendConfiguration')} withHeader dropdown dataUrl={'rest/send-configurations-table/' + getNamespaceIdFilterCookie()} columns={sendConfigurationsColumns} selectionLabelIndex={1} help={t('sendConfigurationThatWillBeUsedFor')}/>
}
return (
<div>
{canDelete &&
@ -268,8 +275,9 @@ export default class CUD extends Component {
<InputField id="contact_email" label={t('contactEmail')} help={t('contactEmailUsedInSubscriptionFormsAnd')}/>
<InputField id="homepage" label={t('homepage')} help={t('homepageUrlUsedInSubscriptionFormsAnd')}/>
{toNameFields}
<TableSelect id="send_configuration" label={t('sendConfiguration')} withHeader dropdown dataUrl='rest/send-configurations-table' columns={sendConfigurationsColumns} selectionLabelIndex={1} help={t('sendConfigurationThatWillBeUsedFor')}/>
{sendConfigTable}
<NamespaceSelect/>
<Dropdown id="form" label={t('forms')} options={formsOptions} help={t('webAndEmailFormsAndTemplatesUsedIn')}/>

View file

@ -9,7 +9,9 @@ import {Icon} from "../lib/bootstrap-components";
import {tableAddDeleteButton, tableRestActionDialogInit, tableRestActionDialogRender} from "../lib/modals";
import {withComponentMixins} from "../lib/decorator-helpers";
import {withForm} from "../lib/form";
import {getNamespaceIdFilterCookie} from "../lib/namespace";
import PropTypes from 'prop-types';
import mailtrainConfig from 'mailtrainConfig';
@withComponentMixins([
withTranslation,
@ -116,6 +118,11 @@ export default class List extends Component {
}
];
var listsTable = <Table ref={node => this.table = node} withHeader dataUrl="rest/lists-table" columns={columns} />;
if(mailtrainConfig.namespaceFilterEnabled && getNamespaceIdFilterCookie()){
listsTable = <Table ref={node => this.table = node} withHeader dataUrl={"rest/lists-table/" + getNamespaceIdFilterCookie()} columns={columns} />;
}
return (
<div>
{tableRestActionDialogRender(this)}
@ -130,7 +137,7 @@ export default class List extends Component {
<Title>{t('lists')}</Title>
<Table ref={node => this.table = node} withHeader dataUrl="rest/lists-table" columns={columns} />
{listsTable}
</div>
);
}

View file

@ -23,7 +23,7 @@ import {
withFormErrorHandlers
} from '../../lib/form';
import {withErrorHandling} from '../../lib/error-handling';
import {getDefaultNamespace, NamespaceSelect, validateNamespace} from '../../lib/namespace';
import {getDefaultNamespace, NamespaceSelect, validateNamespace, getNamespaceIdFilterCookie} from '../../lib/namespace';
import {DeleteModalDialog} from "../../lib/modals";
import mailtrainConfig from 'mailtrainConfig';
import {getTrustedUrl, getUrl} from "../../lib/urls";
@ -486,6 +486,13 @@ export default class CUD extends Component {
const previewListId = this.getFormValue('previewList');
const selectedTemplate = this.getFormValue('selectedTemplate');
var customFormsTable = <TableSelect id="existingEntity" label={t('Source custom forms')} withHeader dropdown dataUrl='rest/forms-table' columns={customFormsColumns} selectionLabelIndex={1} />;
var listsTable = <TableSelect id="previewList" label={t('listToPreviewOn')} withHeader dropdown dataUrl='rest/lists-table' columns={listsColumns} selectionLabelIndex={1} help={t('selectListWhoseFieldsWillBeUsedToPreview')}/>;
if(mailtrainConfig.namespaceFilterEnabled && getNamespaceIdFilterCookie()){
customFormsTable = <TableSelect id="existingEntity" label={t('Source custom forms')} withHeader dropdown dataUrl={'rest/forms-table/' +getNamespaceIdFilterCookie()} columns={customFormsColumns} selectionLabelIndex={1} />;
listsTable = <TableSelect id="previewList" label={t('listToPreviewOn')} withHeader dropdown dataUrl={'rest/lists-table/' + getNamespaceIdFilterCookie()} columns={listsColumns} selectionLabelIndex={1} help={t('selectListWhoseFieldsWillBeUsedToPreview')}/>;
}
return (
<div className={this.state.previewFullscreen ? styles.withElementInFullscreen : ''}>
{canDelete &&
@ -512,12 +519,12 @@ export default class CUD extends Component {
<CheckBox id="fromExistingEntity" label={t('customForms')} text={t('cloneFromAnExistingCustomForms')}/>
}
{this.getFormValue('fromExistingEntity') ?
<TableSelect id="existingEntity" label={t('Source custom forms')} withHeader dropdown dataUrl='rest/forms-table' columns={customFormsColumns} selectionLabelIndex={1} />
:
{this.getFormValue('fromExistingEntity') && customFormsTable}
{!this.getFormValue('fromExistingEntity') &&
<>
<Fieldset label={t('formsPreview')}>
<TableSelect id="previewList" label={t('listToPreviewOn')} withHeader dropdown dataUrl='rest/lists-table' columns={listsColumns} selectionLabelIndex={1} help={t('selectListWhoseFieldsWillBeUsedToPreview')}/>
{listsTable}
{ previewListId &&
<div>

View file

@ -9,6 +9,8 @@ import {Icon} from "../../lib/bootstrap-components";
import {tableAddDeleteButton, tableRestActionDialogInit, tableRestActionDialogRender} from "../../lib/modals";
import {withComponentMixins} from "../../lib/decorator-helpers";
import PropTypes from 'prop-types';
import {getNamespaceIdFilterCookie} from '../../lib/namespace';
import mailtrainConfig from 'mailtrainConfig';
@withComponentMixins([
withTranslation,
@ -62,7 +64,10 @@ export default class List extends Component {
}
}
];
var customFormsTable = <Table ref={node => this.table = node} withHeader dataUrl="rest/forms-table" columns={columns} />;
if(mailtrainConfig.namespaceFilterEnabled && getNamespaceIdFilterCookie()){
customFormsTable = <Table ref={node => this.table = node} withHeader dataUrl={"rest/forms-table/" + getNamespaceIdFilterCookie()} columns={columns} />;
}
return (
<div>
{tableRestActionDialogRender(this)}
@ -74,7 +79,7 @@ export default class List extends Component {
<Title>{t('forms')}</Title>
<Table ref={node => this.table = node} withHeader dataUrl="rest/forms-table" columns={columns} />
{customFormsTable}
</div>
);
}

View file

@ -0,0 +1,174 @@
'use strict';
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {withTranslation} from '../lib/i18n';
import {requiresAuthenticatedUser, Title, withPageHelpers} from '../lib/page';
import {
Button,
ButtonRow,
filterData,
Form,
FormSendMethod,
TreeTableSelect,
withForm,
withFormErrorHandlers
} from '../lib/form';
import axios from '../lib/axios';
import {withAsyncErrorHandler, withErrorHandling} from '../lib/error-handling';
import {getGlobalNamespaceId} from "../../../shared/namespaces";
import {getUrl} from "../lib/urls";
import {withComponentMixins} from "../lib/decorator-helpers";
import {getDefaultNamespace} from "../lib/namespace";
import mailtrainConfig from 'mailtrainConfig';
@withComponentMixins([
withTranslation,
withForm,
withErrorHandling,
withPageHelpers,
requiresAuthenticatedUser
])
export default class Filter extends Component {
constructor(props) {
super(props);
this.state = {};
this.initForm();
}
static propTypes = {
action: PropTypes.string.isRequired,
entity: PropTypes.object,
permissions: PropTypes.object
}
submitFormValuesMutator(data) {
return filterData(data, ['name', 'description', 'namespace']);
}
isEditGlobal() {
return this.props.entity && this.props.entity.id === getGlobalNamespaceId();
}
removeNsIdSubtree(data) {
for (let idx = 0; idx < data.length; idx++) {
const entry = data[idx];
if (entry.key === this.props.entity.id) {
data.splice(idx, 1);
return true;
}
if (this.removeNsIdSubtree(entry.children)) {
return true;
}
}
}
@withAsyncErrorHandler
async loadTreeData() {
if (!this.isEditGlobal()) {
const response = await axios.get(getUrl('rest/namespaces-tree'));
const data = response.data;
for (const root of data) {
root.expanded = true;
}
if (this.props.entity && !this.isEditGlobal()) {
this.removeNsIdSubtree(data);
}
if (this.isComponentMounted()) {
this.setState({
treeData: data
});
}
}
}
componentDidMount() {
if (this.props.entity) {
this.getFormValuesFromEntity(this.props.entity);
} else {
this.populateFormValues({
name: '',
description: '',
namespace: getDefaultNamespace(this.props.permissions)
});
}
// noinspection JSIgnoredPromiseFromCall
this.loadTreeData();
}
localValidateFormValues(state) {
const t = this.props.t;
if (!state.getIn(['namespace', 'value'])) {
state.setIn(['namespace', 'error'], t('namespaceMustBeSelected'));
} else {
state.setIn(['namespace', 'error'], null);
}
}
@withFormErrorHandlers
async submitHandler(leave, disallow) {
if(disallow){
document.cookie = 'namespaceFilterId' + '=; expires=Thu, 01 Jan 1970 00:00:01 GMT;';//Delete namespaceFilter cookies
document.cookie = 'namespaceFilterName' + '=; expires=Thu, 01 Jan 1970 00:00:01 GMT;';
location.reload();
}else{
const t = this.props.t;
let sendMethod, url;
sendMethod = FormSendMethod.GET;
if(this.getFormValue('namespace')){
url = 'rest/namespaces/' + this.getFormValue('namespace');
this.disableForm();
this.setFormStatusMessage('info', t('selecting'));
const submitResult = await this.validateAndSendFormValuesToURL(sendMethod, url);
document.cookie = "namespaceFilterId=" + this.getFormValue('namespace') + ";";
document.cookie = "namespaceFilterName=" + submitResult.name + ";";
if (submitResult) {
if(leave) {
history.back();
}else{
location.reload();
}
}else {
this.enableForm();
this.setFormStatusMessage('warning', t('thereAreErrorsInTheFormPleaseFixThemAnd'));
}
}else{
this.setFormStatusMessage('warning', t('namespaceMustBeSelected'));
}
}
}
render() {
const t = this.props.t;
return (
<div>
<Title>{t('namespaceFilter')}</Title>
<Form stateOwner={this} onSubmitAsync={::this.submitHandler}>
<TreeTableSelect id="namespace" label={t('namespace')} data={this.state.treeData}/>
<ButtonRow>
<Button type="submit" className="btn-primary" icon="check" label={t('select')} onClickAsync={async () => await this.submitHandler(false,false)}/>
<Button type="submit" className="btn-primary" icon="check" label={t('selectAndReturn')} onClickAsync={async () => await this.submitHandler(true, false)}/>
<Button type="submit" className="btn-danger" icon="times" label={t('disallow')} onClickAsync={async () => await this.submitHandler(null,true)}/>
</ButtonRow>
</Form>
</div>
);
}
}

View file

@ -11,6 +11,7 @@ import {getGlobalNamespaceId} from "../../../shared/namespaces";
import {withComponentMixins} from "../lib/decorator-helpers";
import mailtrainConfig from 'mailtrainConfig';
import PropTypes from 'prop-types';
import {getNamespaceIdFilterCookie} from '../lib/namespace';
@withComponentMixins([
withTranslation,
@ -61,6 +62,12 @@ export default class List extends Component {
return actions;
};
var namespacesTree = <TreeTable ref={node => this.table = node} withHeader withDescription dataUrl="rest/namespaces-tree" actions={actions} />
if(mailtrainConfig.namespaceFilterEnabled && getNamespaceIdFilterCookie()){
namespacesTree = <TreeTable ref={node => this.table = node} withHeader withDescription dataUrl={"rest/namespaces-tree/" + getNamespaceIdFilterCookie()} actions={actions} />
}
return (
<div>
{tableRestActionDialogRender(this)}
@ -72,7 +79,7 @@ export default class List extends Component {
<Title>{t('namespaces')}</Title>
<TreeTable ref={node => this.table = node} withHeader withDescription dataUrl="rest/namespaces-tree" actions={actions} />
{namespacesTree}
</div>
);
}

View file

@ -1,54 +1,106 @@
'use strict';
import React from 'react';
import CUD from './CUD';
import List from './List';
import CUD from "./CUD";
import List from './List'
import Filter from "./Filter";
import Share from '../shares/Share';
import {ellipsizeBreadcrumbLabel} from "../lib/helpers";
import {namespaceCheckPermissions} from "../lib/namespace";
import mailtrainConfig from "mailtrainConfig";
function getMenus(t) {
return {
namespaces: {
title: t('namespaces'),
link: '/namespaces',
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)}),
resolve: {
namespace: params => `rest/namespaces/${params.namespaceId}`
if(mailtrainConfig.namespaceFilterEnabled){
return {
namespaces: {
title: t('namespaces'),
link: '/namespaces',
checkPermissions: {
createNamespace: {
entityTypeId: 'namespace',
requiredOperations: ['createNamespace']
},
link: params => `/namespaces/${params.namespaceId}/edit`,
navs: {
':action(edit|delete)': {
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} permissions={props.permissions} />
...namespaceCheckPermissions('createNamespace')
},
panelRender: props => <List permissions={props.permissions}/>,
children: {
':namespaceId([0-9]+)': {
title: resolved => t('namespaceName', {name: ellipsizeBreadcrumbLabel(resolved.namespace.name)}),
resolve: {
namespace: params => `rest/namespaces/${params.namespaceId}`
},
share: {
title: t('share'),
link: params => `/namespaces/${params.namespaceId}/share`,
visible: resolved => resolved.namespace.permissions.includes('share'),
panelRender: props => <Share title={t('share')} entity={props.resolved.namespace} entityTypeId="namespace" />
link: params => `/namespaces/${params.namespaceId}/edit`,
navs: {
':action(edit|delete)': {
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} permissions={props.permissions} />
},
share: {
title: t('share'),
link: params => `/namespaces/${params.namespaceId}/share`,
visible: resolved => resolved.namespace.permissions.includes('share'),
panelRender: props => <Share title={t('share')} entity={props.resolved.namespace} entityTypeId="namespace" />
}
}
}
},
create: {
title: t('create'),
panelRender: props => <CUD action="create" permissions={props.permissions} />
},
},
filter: {
title: t('filter'),
panelRender: props => <Filter action="filter" permissions={props.permissions} />
},
create: {
title: t('create'),
panelRender: props => <CUD action="create" permissions={props.permissions} />
},
}
}
}
};
};
}
else{
return {
namespaces: {
title: t('namespaces'),
link: '/namespaces',
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)}),
resolve: {
namespace: params => `rest/namespaces/${params.namespaceId}`
},
link: params => `/namespaces/${params.namespaceId}/edit`,
navs: {
':action(edit|delete)': {
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} permissions={props.permissions} />
},
share: {
title: t('share'),
link: params => `/namespaces/${params.namespaceId}/share`,
visible: resolved => resolved.namespace.permissions.includes('share'),
panelRender: props => <Share title={t('share')} entity={props.resolved.namespace} entityTypeId="namespace" />
}
}
},
create: {
title: t('create'),
panelRender: props => <CUD action="create" permissions={props.permissions} />
},
}
}
};
}
}

View file

@ -25,6 +25,8 @@ import {getDefaultNamespace, NamespaceSelect, validateNamespace} from '../lib/na
import {DeleteModalDialog} from "../lib/modals";
import {getUrl} from "../lib/urls";
import {withComponentMixins} from "../lib/decorator-helpers";
import {getNamespaceIdFilterCookie} from "../lib/namespace";
import mailtrainConfig from 'mailtrainConfig';
@withComponentMixins([
withTranslation,

View file

@ -13,6 +13,8 @@ import {getUrl} from "../lib/urls";
import {tableAddDeleteButton, tableRestActionDialogInit, tableRestActionDialogRender} from "../lib/modals";
import {withComponentMixins} from "../lib/decorator-helpers";
import PropTypes from 'prop-types';
import {getNamespaceIdFilterCookie} from '../lib/namespace';
import mailtrainConfig from 'mailtrainConfig';
@withComponentMixins([
withTranslation,
@ -147,6 +149,11 @@ export default class List extends Component {
}
];
var reportsTable = <Table ref={node => this.table = node} withHeader dataUrl="rest/reports-table" columns={columns} />;
if(mailtrainConfig.namespaceFilterEnabled && getNamespaceIdFilterCookie()){
reportsTable = <Table ref={node => this.table = node} withHeader dataUrl={"rest/reports-table/" + getNamespaceIdFilterCookie()} columns={columns} />;
}
return (
<div>
@ -162,7 +169,7 @@ export default class List extends Component {
<Title>{t('reports')}</Title>
<Table ref={node => this.table = node} withHeader dataUrl="rest/reports-table" columns={columns} />
{reportsTable}
</div>
);
}

View file

@ -11,6 +11,7 @@ import mailtrainConfig from 'mailtrainConfig';
import {tableAddDeleteButton, tableRestActionDialogInit, tableRestActionDialogRender} from "../../lib/modals";
import {withComponentMixins} from "../../lib/decorator-helpers";
import PropTypes from 'prop-types';
import {getNamespaceIdFilterCookie} from '../../lib/namespace';
@withComponentMixins([
withTranslation,
@ -67,6 +68,12 @@ export default class List extends Component {
}
];
var reportTemplatesTable = <Table ref={node => this.table = node} withHeader dataUrl="rest/report-templates-table" columns={columns} />
if(mailtrainConfig.namespaceFilterEnabled && getNamespaceIdFilterCookie()){
reportTemplatesTable = <Table ref={node => this.table = node} withHeader dataUrl={"rest/report-templates-table/" + getNamespaceIdFilterCookie()} columns={columns} />
}
return (
<div>
{tableRestActionDialogRender(this)}
@ -82,8 +89,7 @@ export default class List extends Component {
}
<Title>{t('reportTemplates')}</Title>
<Table ref={node => this.table = node} withHeader dataUrl="rest/report-templates-table" columns={columns} />
{reportTemplatesTable}
</div>
);
}

View file

@ -17,7 +17,8 @@ import users from './users/root';
import sendConfigurations from './send-configurations/root';
import settings from './settings/root';
import {DropdownLink, getLanguageChooser, NavDropdown, NavLink, Section} from "./lib/page";
import {DropdownLink, getLanguageChooser, getNamespaceChooser, NavDropdown, NavLink, Section} from "./lib/page";
import {getNamespaceNameFilterCookie} from "./lib/namespace";
import mailtrainConfig from 'mailtrainConfig';
import Home from "./Home";
@ -56,6 +57,10 @@ class Root extends Component {
async logout() {
await axios.post(getUrl('rest/logout'));
window.location = getUrl();
if(mailtrainConfig.namespaceFilterEnabled){
document.cookie = 'namespaceFilterId' + '=; expires=Thu, 01 Jan 1970 00:00:01 GMT;';//Delete namespaceFilter cookies
document.cookie = 'namespaceFilterName' + '=; expires=Thu, 01 Jan 1970 00:00:01 GMT;';
}
}
render() {
@ -64,7 +69,17 @@ class Root extends Component {
const topLevelItems = structure.children;
const topLevelMenu = [];
var namespaceFilter = null;
if(mailtrainConfig.namespaceFilterEnabled){
if(getNamespaceNameFilterCookie()){
namespaceFilter = <NavLink to={'/namespaces/filter'}>{getNamespaceNameFilterCookie()}</NavLink>;
}else{
namespaceFilter = <NavLink to={'/namespaces/filter'}>{'Namespace filter'}</NavLink>;
}
}
for (const entryKey of topLevelMenuKeys) {
const entry = topLevelItems[entryKey];
const link = entry.link || entry.externalLink;
@ -91,6 +106,7 @@ class Root extends Component {
</NavDropdown>
</ul>
<ul className="navbar-nav mt-navbar-nav-right">
{namespaceFilter}
{getLanguageChooser(t)}
<NavDropdown menuClassName="dropdown-menu-right" label={mailtrainConfig.user.username} icon="user">
<DropdownLink to="/account"><Icon icon='user'/> {t('account')}</DropdownLink>

View file

@ -11,6 +11,8 @@ import {getMailerTypes} from './helpers';
import {tableAddDeleteButton, tableRestActionDialogInit, tableRestActionDialogRender} from "../lib/modals";
import {withComponentMixins} from "../lib/decorator-helpers";
import PropTypes from 'prop-types';
import {getNamespaceIdFilterCookie} from '../lib/namespace';
import mailtrainConfig from 'mailtrainConfig';
@withComponentMixins([
@ -72,6 +74,12 @@ export default class List extends Component {
}
];
var sendConfigTable = <Table ref={node => this.table = node} withHeader dataUrl="rest/send-configurations-table" columns={columns} />
if(mailtrainConfig.namespaceFilterEnabled && getNamespaceIdFilterCookie()){
sendConfigTable = <Table ref={node => this.table = node} withHeader dataUrl={'rest/send-configurations-table/' + getNamespaceIdFilterCookie()} columns={columns} />
}
return (
<div>
{tableRestActionDialogRender(this)}
@ -83,7 +91,7 @@ export default class List extends Component {
<Title>{t('sendConfigurations-1')}</Title>
<Table ref={node => this.table = node} withHeader dataUrl="rest/send-configurations-table" columns={columns} />
{sendConfigTable}
</div>
);
}

View file

@ -20,7 +20,7 @@ import {
withFormErrorHandlers
} from '../lib/form';
import {withErrorHandling} from '../lib/error-handling';
import {getDefaultNamespace, NamespaceSelect, validateNamespace} from '../lib/namespace';
import {getDefaultNamespace, NamespaceSelect, validateNamespace, getNamespaceIdFilterCookie} from '../lib/namespace';
import {ContentModalDialog, DeleteModalDialog} from "../lib/modals";
import mailtrainConfig from 'mailtrainConfig';
import {getEditForm, getTagLanguages, getTemplateTypes, getTypeForm} from './helpers';
@ -325,6 +325,12 @@ export default class CUD extends Component {
{ data: 6, title: t('namespace') },
];
var sourceTemplateTable = <TableSelect id="existingEntity" label={t('Source template')} withHeader dropdown dataUrl='rest/templates-table' columns={templatesColumns} selectionLabelIndex={1} />;
if(mailtrainConfig.namespaceFilterEnabled && getNamespaceIdFilterCookie()){
sourceTemplateTable = <TableSelect id="existingEntity" label={t('Source template')} withHeader dropdown dataUrl={'rest/templates-table/'+getNamespaceIdFilterCookie()} columns={templatesColumns} selectionLabelIndex={1} />;
}
return (
<div className={this.state.elementInFullscreen ? styles.withElementInFullscreen : ''}>
{isEdit &&
@ -362,9 +368,9 @@ export default class CUD extends Component {
<CheckBox id="fromExistingEntity" label={t('template')} text={t('cloneFromAnExistingTemplate')}/>
}
{this.getFormValue('fromExistingEntity') ?
<TableSelect id="existingEntity" label={t('Source template')} withHeader dropdown dataUrl='rest/templates-table' columns={templatesColumns} selectionLabelIndex={1} />
:
{this.getFormValue('fromExistingEntity') && sourceTemplateTable }
{!this.getFormValue('fromExistingEntity') &&
<>
{isEdit ?
<StaticField id="type" className={styles.formDisabled} label={t('type')}>

View file

@ -11,7 +11,8 @@ import {getTagLanguages, getTemplateTypes} from './helpers';
import {tableAddDeleteButton, tableRestActionDialogInit, tableRestActionDialogRender} from "../lib/modals";
import {withComponentMixins} from "../lib/decorator-helpers";
import PropTypes from 'prop-types';
import {getNamespaceIdFilterCookie} from "../lib/namespace";
import mailtrainConfig from 'mailtrainConfig';
@withComponentMixins([
withTranslation,
@ -81,6 +82,11 @@ export default class List extends Component {
}
];
var templatesTable = <Table ref={node => this.table = node} withHeader dataUrl="rest/templates-table" columns={columns} />;
if(mailtrainConfig.namespaceFilterEnabled && getNamespaceIdFilterCookie()){
templatesTable = <Table ref={node => this.table = node} withHeader dataUrl={"rest/templates-table/" + getNamespaceIdFilterCookie()} columns={columns} />;
}
return (
<div>
{tableRestActionDialogRender(this)}
@ -95,7 +101,7 @@ export default class List extends Component {
<Title>{t('templates')}</Title>
<Table ref={node => this.table = node} withHeader dataUrl="rest/templates-table" columns={columns} />
{templatesTable}
</div>
);
}

View file

@ -22,6 +22,7 @@ import {Trans} from "react-i18next";
import {TagLanguages, renderTag} from "../../../shared/templates";
import styles from "../lib/styles.scss";
import {getNamespaceIdFilterCookie } from "../lib/namespace";
export const ResourceType = {
TEMPLATE: 'template',
@ -89,7 +90,18 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
getTypeForm: (owner, isEdit) => {
const tagLanguageKey = owner.getFormValue(prefix + 'tag_language');
if (tagLanguageKey) {
return <TableSelect
if(mailtrainConfig.namespaceFilterEnabled && getNamespaceIdFilterCookie()){
return <TableSelect
id={prefix + 'mosaicoTemplate'}
label={t('mosaicoTemplate')}
withHeader
dropdown
dataUrl={`rest/mosaico-templates-by-tag-language-table/${tagLanguageKey}/` + getNamespaceIdFilterCookie()}
columns={mosaicoTemplatesColumns}
selectionLabelIndex={1}
disabled={isEdit}/>
}else{
return <TableSelect
id={prefix + 'mosaicoTemplate'}
label={t('mosaicoTemplate')}
withHeader
@ -98,6 +110,7 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
columns={mosaicoTemplatesColumns}
selectionLabelIndex={1}
disabled={isEdit}/>
}
} else {
return null;
}

View file

@ -12,6 +12,9 @@ import {getTagLanguages} from '../helpers';
import {tableAddDeleteButton, tableRestActionDialogInit, tableRestActionDialogRender} from "../../lib/modals";
import {withComponentMixins} from "../../lib/decorator-helpers";
import PropTypes from 'prop-types';
import {getNamespaceIdFilterCookie} from '../../lib/namespace';
import mailtrainConfig from 'mailtrainConfig';
import { id } from 'brace/worker/css';
@withComponentMixins([
@ -88,6 +91,11 @@ export default class List extends Component {
}
];
var mosaicoTemplatesTable = <Table ref={node => this.table = node} withHeader dataUrl="rest/mosaico-templates-table" columns={columns} />;
if(mailtrainConfig.namespaceFilterEnabled && getNamespaceIdFilterCookie()){
mosaicoTemplatesTable = <Table ref={node => this.table = node} withHeader dataUrl={"rest/mosaico-templates-table/" + getNamespaceIdFilterCookie()} columns={columns}/>
}
return (
<div>
{tableRestActionDialogRender(this)}
@ -103,7 +111,7 @@ export default class List extends Component {
<Title>{t('mosaicoTemplates')}</Title>
<Table ref={node => this.table = node} withHeader dataUrl="rest/mosaico-templates-table" columns={columns} />
{mosaicoTemplatesTable}
</div>
);
}

View file

@ -8,6 +8,7 @@ import mailtrainConfig from "mailtrainConfig";
import {Icon} from "../lib/bootstrap-components";
import {tableAddDeleteButton, tableRestActionDialogInit, tableRestActionDialogRender} from "../lib/modals";
import {withComponentMixins} from "../lib/decorator-helpers";
import {getNamespaceIdFilterCookie} from '../lib/namespace';
@withComponentMixins([
withTranslation,
@ -59,6 +60,12 @@ export default class List extends Component {
}
});
var usersTable = <Table ref={node => this.table = node} withHeader dataUrl="rest/users-table" columns={columns} />
if(mailtrainConfig.namespaceFilterEnabled && getNamespaceIdFilterCookie()){
usersTable = <Table ref={node => this.table = node} withHeader dataUrl={'rest/users-table/' + getNamespaceIdFilterCookie()} columns={columns} />
}
return (
<div>
{tableRestActionDialogRender(this)}
@ -67,8 +74,7 @@ export default class List extends Component {
</Toolbar>
<Title>{t('users')}</Title>
<Table ref={node => this.table = node} withHeader dataUrl="rest/users-table" columns={columns} />
{usersTable}
</div>
);
}

View file

@ -1024,5 +1024,9 @@
"thePasswordMustContainAtLeastOne": "The password must contain at least one lowercase letter",
"thePasswordMustContainAtLeastOne-1": "The password must contain at least one uppercase letter",
"thePasswordMustContainAtLeastOneNumber": "The password must contain at least one number",
"thePasswordMustContainAtLeastOneSpecial": "The password must contain at least one special character"
"thePasswordMustContainAtLeastOneSpecial": "The password must contain at least one special character",
"namespaceFilter": "Namespace filter",
"filter": "Filter",
"disallow": "Disallow",
"selectAndReturn": "Select and return"
}

View file

@ -1024,5 +1024,9 @@
"thePasswordMustContainAtLeastOne": "The password must contain at least one lowercase letter",
"thePasswordMustContainAtLeastOne-1": "The password must contain at least one uppercase letter",
"thePasswordMustContainAtLeastOneNumber": "The password must contain at least one number",
"thePasswordMustContainAtLeastOneSpecial": "The password must contain at least one special character"
"thePasswordMustContainAtLeastOneSpecial": "The password must contain at least one special character",
"namespaceFilter": "Namespace filter",
"filter": "Filter",
"disallow": "Disallow",
"selectAndReturn": "Select and return"
}

View file

@ -1103,5 +1103,9 @@
"thePasswordMustContainAtLeastOne": "The password must contain at least one lowercase letter",
"thePasswordMustContainAtLeastOne-1": "The password must contain at least one uppercase letter",
"thePasswordMustContainAtLeastOneNumber": "The password must contain at least one number",
"thePasswordMustContainAtLeastOneSpecial": "The password must contain at least one special character"
"thePasswordMustContainAtLeastOneSpecial": "The password must contain at least one special character",
"namespaceFilter": "Filtrado por espacio de nombres",
"filter": "Filtro",
"disallow": "Desactivar",
"selectAndReturn": "Seleccionar y volver"
}

View file

@ -54,6 +54,9 @@ enabledLanguages:
# Inject custom scripts in subscription/layout.mjml.hbs
# customSubscriptionScripts: [/custom/hello-world.js]
#Enable to use Namespace Filter
namespaceFilter:
enabled: false
# Enable to use Redis session cache or disable if Redis is not installed
redis:

View file

@ -23,6 +23,7 @@ async function getAnonymousConfig(context, appType) {
sandboxUrlBaseDir: urls.getSandboxUrlBaseDir(),
publicUrlBase: urls.getPublicUrlBase(),
publicUrlBaseDir: urls.getPublicUrlBaseDir(),
namespaceFilterEnabled: config.namespaceFilter.enabled,
appType
}
}
@ -49,7 +50,8 @@ async function getAuthenticatedConfig(context) {
verpEnabled: config.verp.enabled,
reportsEnabled: config.reports.enabled,
mapsApiKey: setts.mapsApiKey,
builtinZoneMTAEnabled: config.builtinZoneMTA.enabled
builtinZoneMTAEnabled: config.builtinZoneMTA.enabled,
namespaceFilterEnabled: config.namespaceFilter.enabled
}
}

View file

@ -24,6 +24,7 @@ const contextHelpers = require('../lib/context-helpers');
const {convertFileURLs} = require('../lib/campaign-content');
const messageSender = require('../lib/message-sender');
const lists = require('./lists');
const namespaces = require('./namespaces');
const {EntityActivityType, CampaignActivityType} = require('../../shared/activity-log');
const activityLog = require('../lib/activity-log');
@ -67,26 +68,32 @@ function hash(entity, content) {
return hasher.hash(filteredEntity);
}
async function _listDTAjax(context, namespaceId, params) {
async function _listDTAjax(context, namespaceFilter, params) {
var allowedNamespaces = [];
if(namespaceFilter){
allowedNamespaces = await namespaces.getAllowedNamespaces(context, namespaceFilter);
}
return await dtHelpers.ajaxListWithPermissions(
context,
[{ entityTypeId: 'campaign', requiredOperations: ['view'] }],
params,
builder => {
builder = builder.from('campaigns')
.innerJoin('namespaces', 'namespaces.id', 'campaigns.namespace')
.whereNull('campaigns.parent');
if (namespaceId) {
builder = builder.where('namespaces.id', namespaceId);
}
.innerJoin('namespaces', 'namespaces.id', 'campaigns.namespace');
for(const key in allowedNamespaces){
builder = builder.orWhere('campaigns.namespace', allowedNamespaces[key]);
}
builder = builder.whereNull('campaigns.parent');
return builder;
},
['campaigns.id', 'campaigns.name', 'campaigns.cid', 'campaigns.description', 'campaigns.type', 'campaigns.status', 'campaigns.scheduled', 'campaigns.source', 'campaigns.created', 'namespaces.name']
);
}
async function listDTAjax(context, params) {
return await _listDTAjax(context, undefined, params);
async function listDTAjax(context, params, namespaceId) {
return await _listDTAjax(context, namespaceId, params);
}
async function listByNamespaceDTAjax(context, namespaceId, params) {
@ -106,14 +113,25 @@ async function listChildrenDTAjax(context, campaignId, params) {
}
async function listWithContentDTAjax(context, params) {
async function listWithContentDTAjax(context, namespaceFilter, params) {
var allowedNamespaces = [];
if(namespaceFilter){
allowedNamespaces = await namespaces.getAllowedNamespaces(context, namespaceFilter);
}
return await dtHelpers.ajaxListWithPermissions(
context,
[{ entityTypeId: 'campaign', requiredOperations: ['view'] }],
params,
builder => builder.from('campaigns')
.innerJoin('namespaces', 'namespaces.id', 'campaigns.namespace')
.whereIn('campaigns.source', [CampaignSource.CUSTOM, CampaignSource.CUSTOM_FROM_TEMPLATE, CampaignSource.CUSTOM_FROM_CAMPAIGN]),
builder => {
builder = builder.from('campaigns')
.innerJoin('namespaces', 'namespaces.id', 'campaigns.namespace');
for(const key in allowedNamespaces){
builder = builder.orWhere('campaigns.namespace', allowedNamespaces[key]);
}
builder = builder.whereIn('campaigns.source', [CampaignSource.CUSTOM, CampaignSource.CUSTOM_FROM_TEMPLATE, CampaignSource.CUSTOM_FROM_CAMPAIGN]);
return builder;
},
['campaigns.id', 'campaigns.name', 'campaigns.cid', 'campaigns.description', 'campaigns.type', 'campaigns.created', 'namespaces.name']
);
}

View file

@ -14,6 +14,7 @@ const mjml2html = require('mjml');
const lists = require('./lists');
const dependencyHelpers = require('../lib/dependency-helpers');
const namespaces = require("./namespaces");
const formAllowedKeys = new Set([
'name',
@ -57,14 +58,28 @@ function hash(entity) {
return hasher.hash(filterObject(entity, hashKeys));
}
async function listDTAjax(context, params) {
async function listDTAjax(context, namespaceFilter, params) {
var allowedNamespaces = [];
if(namespaceFilter){
allowedNamespaces = await namespaces.getAllowedNamespaces(context, namespaceFilter);
}
return await dtHelpers.ajaxListWithPermissions(
context,
[{ entityTypeId: 'customForm', requiredOperations: ['view'] }],
params,
builder => builder
.from('custom_forms')
.innerJoin('namespaces', 'namespaces.id', 'custom_forms.namespace'),
builder => {
builder = builder
.from('custom_forms')
.innerJoin('namespaces', 'namespaces.id', 'custom_forms.namespace');
if (namespaceFilter) {
for(const key in allowedNamespaces){
builder = builder.orWhere('namespaces.id', allowedNamespaces[key]);
}
}
return builder;
},
['custom_forms.id', 'custom_forms.name', 'custom_forms.description', 'namespaces.name']
);
}

View file

@ -13,6 +13,7 @@ const segments = require('./segments');
const imports = require('./imports');
const entitySettings = require('../lib/entity-settings');
const dependencyHelpers = require('../lib/dependency-helpers');
const namespaces = require('./namespaces');
const {EntityActivityType} = require('../../shared/activity-log');
const activityLog = require('../lib/activity-log');
@ -26,8 +27,13 @@ function hash(entity) {
}
async function _listDTAjax(context, namespaceId, params) {
async function _listDTAjax(context, namespaceFilter, params) {
const campaignEntityType = entitySettings.getEntityType('campaign');
var allowedNamespaces = [];
if(namespaceFilter){
allowedNamespaces = await namespaces.getAllowedNamespaces(context, namespaceFilter);
}
return await dtHelpers.ajaxListWithPermissions(
context,
@ -37,9 +43,11 @@ async function _listDTAjax(context, namespaceId, params) {
builder = builder
.from('lists')
.innerJoin('namespaces', 'namespaces.id', 'lists.namespace');
if (namespaceId) {
builder = builder.where('lists.namespace', namespaceId);
}
if (namespaceFilter) {
for(const key in allowedNamespaces){
builder = builder.orWhere('lists.namespace', allowedNamespaces[key]);
}
}
return builder;
},
['lists.id', 'lists.name', 'lists.cid', 'lists.subscribers', 'lists.description', 'namespaces.name',
@ -59,11 +67,7 @@ async function _listDTAjax(context, namespaceId, params) {
);
}
async function listDTAjax(context, params) {
return await _listDTAjax(context, undefined, params);
}
async function listByNamespaceDTAjax(context, namespaceId, params) {
async function listDTAjax(context, namespaceId, params) {
return await _listDTAjax(context, namespaceId, params);
}
@ -274,7 +278,6 @@ async function remove(context, id) {
module.exports.UnsubscriptionMode = UnsubscriptionMode;
module.exports.hash = hash;
module.exports.listDTAjax = listDTAjax;
module.exports.listByNamespaceDTAjax = listByNamespaceDTAjax;
module.exports.listWithSegmentByCampaignDTAjax = listWithSegmentByCampaignDTAjax;
module.exports.getByIdTx = getByIdTx;
module.exports.getById = getById;

View file

@ -10,6 +10,7 @@ const shares = require('./shares');
const files = require('./files');
const dependencyHelpers = require('../lib/dependency-helpers');
const { allTagLanguages } = require('../../shared/templates');
const namespaces = require('./namespaces');
const allowedKeys = new Set(['name', 'description', 'type', 'tag_language', 'data', 'namespace']);
@ -27,24 +28,52 @@ async function getById(context, id) {
});
}
async function listDTAjax(context, params) {
async function listDTAjax(context, namespaceFilter, params) {
var allowedNamespaces = [];
if(namespaceFilter){
allowedNamespaces = await namespaces.getAllowedNamespaces(context, namespaceFilter);
}
return await dtHelpers.ajaxListWithPermissions(
context,
[{ entityTypeId: 'mosaicoTemplate', requiredOperations: ['view'] }],
params,
builder => builder.from('mosaico_templates').innerJoin('namespaces', 'namespaces.id', 'mosaico_templates.namespace'),
builder => {
builder = builder
.from('mosaico_templates')
.innerJoin('namespaces', 'namespaces.id', 'mosaico_templates.namespace');
if (namespaceFilter) {
for(const key in allowedNamespaces){
builder = builder.orWhere('namespaces.id', allowedNamespaces[key]);
}
}
return builder;
},
[ 'mosaico_templates.id', 'mosaico_templates.name', 'mosaico_templates.description', 'mosaico_templates.type', 'mosaico_templates.tag_language', 'mosaico_templates.created', 'namespaces.name' ]
);
}
async function listByTagLanguageDTAjax(context, tagLanguage, params) {
async function listByTagLanguageDTAjax(context, tagLanguage, namespaceFilter, params) {
var allowedNamespaces = [];
if(namespaceFilter){
allowedNamespaces = await namespaces.getAllowedNamespaces(context, namespaceFilter);
}
return await dtHelpers.ajaxListWithPermissions(
context,
[{ entityTypeId: 'mosaicoTemplate', requiredOperations: ['view'] }],
params,
builder => builder.from('mosaico_templates')
.innerJoin('namespaces', 'namespaces.id', 'mosaico_templates.namespace')
.where('mosaico_templates.tag_language', tagLanguage),
builder => {
builder = builder.from('mosaico_templates')
.innerJoin('namespaces', 'namespaces.id', 'mosaico_templates.namespace');
if (namespaceFilter) {
for(const key in allowedNamespaces){
builder = builder.orWhere('namespaces.id', allowedNamespaces[key]);
}
}
builder = builder.where('mosaico_templates.tag_language', tagLanguage);
return builder;
},
[ 'mosaico_templates.id', 'mosaico_templates.name', 'mosaico_templates.description', 'mosaico_templates.type', 'mosaico_templates.tag_language', 'mosaico_templates.created', 'namespaces.name' ]
);
}

View file

@ -12,7 +12,8 @@ const dependencyHelpers = require('../lib/dependency-helpers');
const allowedKeys = new Set(['name', 'description', 'namespace']);
async function listTree(context) {
async function listTree(context, nsId, search) {
enforce(!context.user.admin, 'listTree is not supposed to be called by assumed admin');
const entityType = entitySettings.getEntityType('namespace');
@ -33,7 +34,7 @@ async function listTree(context) {
'namespaces.id', 'namespaces.name', 'namespaces.description', 'namespaces.namespace',
knex.raw(`GROUP_CONCAT(${entityType.permissionsTable + '.operation'} SEPARATOR \';\') as permissions`)
]);
const entries = {};
for (let row of rows) {
@ -102,6 +103,30 @@ async function listTree(context) {
delete entry.parent;
}
if(nsId && !search){
var root = [];
function process_node(node,topNamespace){
var branch = false;
if(node){
if(node.key == topNamespace){
branch = true;
return node;
}
if(node.children && !branch){
for(const key in node.children){
const n = process_node(node.children[key], topNamespace);
if(n){
return n;
}
}
}
}
return null;
}
root.push(process_node(roots[0], nsId));
return root;
}
return roots;
}
@ -241,6 +266,35 @@ async function remove(context, id) {
});
}
async function getAllowedNamespaces(context, topNamespace){
const tree = await listTree(context, null, true);
var allowedNamespaces = [];
function process_node(node, namespaces, branch, topNamespace){
if(node){
if(branch){
namespaces.push(node.key);
}
if(node.key == topNamespace){
branch = true;
namespaces.push(node.key);
}
if(node.children){
for(const key in node.children){
process_node(node.children[key], namespaces, branch, topNamespace);
}
}
}
}
if(tree && topNamespace){
process_node(tree[0], allowedNamespaces, false, topNamespace);
allowedNamespaces.sort();
}
return allowedNamespaces;
}
module.exports.hash = hash;
module.exports.listTree = listTree;
module.exports.getById = getById;
@ -249,3 +303,4 @@ module.exports.create = create;
module.exports.createTx = createTx;
module.exports.updateWithConsistencyCheck = updateWithConsistencyCheck;
module.exports.remove = remove;
module.exports.getAllowedNamespaces = getAllowedNamespaces;

View file

@ -9,6 +9,7 @@ const namespaceHelpers = require('../lib/namespace-helpers');
const shares = require('./shares');
const reports = require('./reports');
const dependencyHelpers = require('../lib/dependency-helpers');
const namespaces = require('./namespaces');
const allowedKeys = new Set(['name', 'description', 'mime_type', 'user_fields', 'js', 'hbs', 'namespace']);
@ -25,12 +26,26 @@ async function getById(context, id) {
});
}
async function listDTAjax(context, params) {
async function listDTAjax(context, namespaceFilter, params) {
var allowedNamespaces = [];
if(namespaceFilter){
allowedNamespaces = await namespaces.getAllowedNamespaces(context, namespaceFilter);
}
return await dtHelpers.ajaxListWithPermissions(
context,
[{ entityTypeId: 'reportTemplate', requiredOperations: ['view'] }],
params,
builder => builder.from('report_templates').innerJoin('namespaces', 'namespaces.id', 'report_templates.namespace'),
builder => {
builder = builder.from('report_templates')
.innerJoin('namespaces', 'namespaces.id', 'report_templates.namespace');
if (namespaceFilter) {
for(const key in allowedNamespaces){
builder = builder.orWhere('namespaces.id', allowedNamespaces[key]);
}
}
return builder;
},
[ 'report_templates.id', 'report_templates.name', 'report_templates.description', 'report_templates.created', 'namespaces.name' ]
);
}

View file

@ -14,6 +14,7 @@ const contextHelpers = require('../lib/context-helpers');
const {LinkId} = require('./links');
const subscriptions = require('./subscriptions');
const {Readable} = require('stream');
const namespaces = require('./namespaces');
const ReportState = require('../../shared/reports').ReportState;
@ -45,7 +46,12 @@ async function getByIdWithTemplate(context, id, withPermissions = true) {
});
}
async function listDTAjax(context, params) {
async function listDTAjax(context, namespaceFilter, params) {
var allowedNamespaces = [];
if(namespaceFilter){
allowedNamespaces = await namespaces.getAllowedNamespaces(context, namespaceFilter);
}
return await dtHelpers.ajaxListWithPermissions(
context,
[
@ -53,9 +59,17 @@ async function listDTAjax(context, params) {
{ entityTypeId: 'reportTemplate', requiredOperations: ['view'] }
],
params,
builder => builder.from('reports')
.innerJoin('report_templates', 'reports.report_template', 'report_templates.id')
.innerJoin('namespaces', 'namespaces.id', 'reports.namespace'),
builder => {
builder = builder.from('reports')
.innerJoin('report_templates', 'reports.report_template', 'report_templates.id')
.innerJoin('namespaces', 'namespaces.id', 'reports.namespace');
if (namespaceFilter) {
for(const key in allowedNamespaces){
builder = builder.orWhere('namespaces.id', allowedNamespaces[key]);
}
}
return builder;
},
[
'reports.id', 'reports.name', 'report_templates.name', 'reports.description',
'reports.last_run', 'namespaces.name', 'reports.state', 'report_templates.mime_type'

View file

@ -13,6 +13,7 @@ const contextHelpers = require('../lib/context-helpers');
const mailers = require('../lib/mailers');
const senders = require('../lib/senders');
const dependencyHelpers = require('../lib/dependency-helpers');
const namespaces = require('./namespaces');
const allowedKeys = new Set(['name', 'description', 'from_email', 'from_email_overridable', 'from_name', 'from_name_overridable', 'reply_to', 'reply_to_overridable', 'x_mailer', 'verp_hostname', 'verp_disable_sender_header', 'mailer_type', 'mailer_settings', 'namespace']);
@ -22,7 +23,14 @@ function hash(entity) {
return hasher.hash(filterObject(entity, allowedKeys));
}
async function _listDTAjax(context, namespaceId, params) {
async function _listDTAjax(context, namespaceFilter, params) {
var allowedNamespaces = [];
if(namespaceFilter){
allowedNamespaces = await namespaces.getAllowedNamespaces(context, namespaceFilter);
}
return await dtHelpers.ajaxListWithPermissions(
context,
[{ entityTypeId: 'sendConfiguration', requiredOperations: ['viewPublic'] }],
@ -31,8 +39,10 @@ async function _listDTAjax(context, namespaceId, params) {
builder = builder
.from('send_configurations')
.innerJoin('namespaces', 'namespaces.id', 'send_configurations.namespace');
if (namespaceId) {
builder = builder.where('send_configurations.namespace', namespaceId);
if (namespaceFilter) {
for(const key in allowedNamespaces){
builder = builder.orWhere('send_configurations.namespace', allowedNamespaces[key]);
}
}
return builder;
},
@ -40,8 +50,8 @@ async function _listDTAjax(context, namespaceId, params) {
);
}
async function listDTAjax(context, params) {
return await _listDTAjax(context, undefined, params);
async function listDTAjax(context, namespaceId, params) {
return await _listDTAjax(context, namespaceId, params);
}
async function listByNamespaceDTAjax(context, namespaceId, params) {

View file

@ -12,6 +12,7 @@ const dependencyHelpers = require('../lib/dependency-helpers');
const {convertFileURLs} = require('../lib/campaign-content');
const { allTagLanguages } = require('../../shared/templates');
const messageSender = require('../lib/message-sender');
const namespaces = require('./namespaces');
const allowedKeys = new Set(['name', 'description', 'type', 'tag_language', 'data', 'html', 'text', 'namespace']);
@ -37,15 +38,23 @@ async function getById(context, id, withPermissions = true) {
});
}
async function _listDTAjax(context, namespaceId, params) {
async function _listDTAjax(context, namespaceFilter, params) {
var allowedNamespaces = [];
if(namespaceFilter){
allowedNamespaces = await namespaces.getAllowedNamespaces(context, namespaceFilter);
}
return await dtHelpers.ajaxListWithPermissions(
context,
[{ entityTypeId: 'template', requiredOperations: ['view'] }],
params,
builder => {
builder = builder.from('templates').innerJoin('namespaces', 'namespaces.id', 'templates.namespace');
if (namespaceId) {
builder = builder.where('namespaces.id', namespaceId);
if (namespaceFilter) {
for(const key in allowedNamespaces){
builder = builder.orWhere('namespaces.id', allowedNamespaces[key]);
}
}
return builder;
},
@ -53,11 +62,7 @@ async function _listDTAjax(context, namespaceId, params) {
);
}
async function listDTAjax(context, params) {
return await _listDTAjax(context, undefined, params);
}
async function listByNamespaceDTAjax(context, namespaceId, params) {
async function listDTAjax(context, namespaceId, params) {
return await _listDTAjax(context, namespaceId, params);
}
@ -177,7 +182,6 @@ module.exports.hash = hash;
module.exports.getByIdTx = getByIdTx;
module.exports.getById = getById;
module.exports.listDTAjax = listDTAjax;
module.exports.listByNamespaceDTAjax = listByNamespaceDTAjax;
module.exports.create = create;
module.exports.updateWithConsistencyCheck = updateWithConsistencyCheck;
module.exports.remove = remove;

View file

@ -14,6 +14,7 @@ const {getTrustedUrl} = require('../lib/urls');
const { tUI } = require('../lib/translate');
const messageSender = require('../lib/message-sender');
const {getSystemSendConfigurationId} = require('../../shared/send-configurations');
const namespaces = require('./namespaces');
const bluebird = require('bluebird');
@ -108,17 +109,35 @@ async function serverValidate(context, data, isOwnAccount) {
return result;
}
async function listDTAjax(context, params) {
async function listDTAjax(context, namespaceFilter, params) {
var allowedNamespaces = [];
if(namespaceFilter){
allowedNamespaces = await namespaces.getAllowedNamespaces(context, namespaceFilter);
}
return await dtHelpers.ajaxListWithPermissions(
context,
[{ entityTypeId: 'namespace', requiredOperations: ['manageUsers'] }],
params,
builder => builder
.from('users')
.innerJoin('namespaces', 'namespaces.id', 'users.namespace')
.innerJoin('generated_role_names', 'generated_role_names.role', 'users.role')
.where('generated_role_names.entity_type', 'global'),
[ 'users.id', 'users.username', 'users.name', 'namespaces.name', 'generated_role_names.name' ]
builder => {
builder = builder
.from('users')
.innerJoin('namespaces', 'namespaces.id', 'users.namespace')
.innerJoin('generated_role_names', 'generated_role_names.role', 'users.role')
if (namespaceFilter) {
for(const key in allowedNamespaces){
builder = builder.orWhere('generated_role_names.entity_type', 'global').andWhere('users.namespace', allowedNamespaces[key]);
}
}else{
builder = builder.where('generated_role_names.entity_type', 'global');
}
return builder;
},
[ 'users.id', 'users.username', 'users.name', 'namespaces.name', 'generated_role_names.name' ]
);
}

View file

@ -6,13 +6,20 @@ const campaigns = require('../../models/campaigns');
const router = require('../../lib/router-async').create();
const {castToInteger} = require('../../lib/helpers');
router.postAsync('/campaigns-table', passport.loggedIn, async (req, res) => {
return res.json(await campaigns.listDTAjax(req.context, req.body));
return res.json(await campaigns.listDTAjax(req.context, req.body, null));
});
router.postAsync('/campaigns-table/:namespaceId', passport.loggedIn, async (req, res) => {
return res.json(await campaigns.listDTAjax(req.context, req.body, castToInteger(req.params.namespaceId)));
});
router.postAsync('/campaigns-with-content-table', passport.loggedIn, async (req, res) => {
return res.json(await campaigns.listWithContentDTAjax(req.context, req.body));
return res.json(await campaigns.listWithContentDTAjax(req.context, null, req.body));
});
router.postAsync('/campaigns-with-content-table/:namespaceId', passport.loggedIn, async (req, res) => {
return res.json(await campaigns.listWithContentDTAjax(req.context, req.params.namespaceId, req.body));
});
router.postAsync('/campaigns-others-by-list-table/:campaignId/:listIds', passport.loggedIn, async (req, res) => {

View file

@ -13,7 +13,11 @@ const {castToInteger} = require('../../lib/helpers');
router.postAsync('/forms-table', passport.loggedIn, async (req, res) => {
return res.json(await forms.listDTAjax(req.context, req.body));
return res.json(await forms.listDTAjax(req.context, null, req.body));
});
router.postAsync('/forms-table/:namespaceId', passport.loggedIn, async (req, res) => {
return res.json(await forms.listDTAjax(req.context, req.params.namespaceId, req.body));
});
router.getAsync('/forms/:formId', passport.loggedIn, async (req, res) => {

View file

@ -8,11 +8,11 @@ const {castToInteger} = require('../../lib/helpers');
router.postAsync('/lists-table', passport.loggedIn, async (req, res) => {
return res.json(await lists.listDTAjax(req.context, req.body));
return res.json(await lists.listDTAjax(req.context, null, req.body));
});
router.postAsync('/lists-by-namespace-table/:namespaceId', passport.loggedIn, async (req, res) => {
return res.json(await lists.listByNamespaceDTAjax(req.context, castToInteger(req.params.namespaceId), req.body));
router.postAsync('/lists-table/:namespaceId', passport.loggedIn, async (req, res) => {
return res.json(await lists.listDTAjax(req.context, castToInteger(req.params.namespaceId), req.body));
});
router.postAsync('/lists-with-segment-by-campaign-table/:campaignId', passport.loggedIn, async (req, res) => {

View file

@ -30,11 +30,19 @@ router.deleteAsync('/mosaico-templates/:mosaicoTemplateId', passport.loggedIn, p
});
router.postAsync('/mosaico-templates-table', passport.loggedIn, async (req, res) => {
return res.json(await mosaicoTemplates.listDTAjax(req.context, req.body));
return res.json(await mosaicoTemplates.listDTAjax(req.context, null, req.body));
});
router.postAsync('/mosaico-templates-table/:namespaceId', passport.loggedIn, async (req, res) => {
return res.json(await mosaicoTemplates.listDTAjax(req.context, req.params.namespaceId, req.body));
});
router.postAsync('/mosaico-templates-by-tag-language-table/:tagLanguage', passport.loggedIn, async (req, res) => {
return res.json(await mosaicoTemplates.listByTagLanguageDTAjax(req.context, req.params.tagLanguage, req.body));
return res.json(await mosaicoTemplates.listByTagLanguageDTAjax(req.context, req.params.tagLanguage, null, req.body));
});
router.postAsync('/mosaico-templates-by-tag-language-table/:tagLanguage/:namespaceId', passport.loggedIn, async (req, res) => {
return res.json(await mosaicoTemplates.listByTagLanguageDTAjax(req.context, req.params.tagLanguage, req.params.namespaceId, req.body));
});

View file

@ -32,9 +32,16 @@ router.deleteAsync('/namespaces/:nsId', passport.loggedIn, passport.csrfProtecti
return res.json();
});
router.getAsync('/namespaces-tree/:nsId', passport.loggedIn, async (req, res) => {
const tree = await namespaces.listTree(req.context, req.params.nsId, false);
return res.json(tree);
});
router.getAsync('/namespaces-tree', passport.loggedIn, async (req, res) => {
const tree = await namespaces.listTree(req.context);
const tree = await namespaces.listTree(req.context, null, false);
return res.json(tree);
});

View file

@ -31,7 +31,11 @@ router.deleteAsync('/report-templates/:reportTemplateId', passport.loggedIn, pas
});
router.postAsync('/report-templates-table', passport.loggedIn, async (req, res) => {
return res.json(await reportTemplates.listDTAjax(req.context, req.body));
return res.json(await reportTemplates.listDTAjax(req.context, null, req.body));
});
router.postAsync('/report-templates-table/:namespaceId', passport.loggedIn, async (req, res) => {
return res.json(await reportTemplates.listDTAjax(req.context, req.params.namespaceId, req.body));
});
router.getAsync('/report-template-user-fields/:reportTemplateId', passport.loggedIn, async (req, res) => {

View file

@ -35,7 +35,11 @@ router.deleteAsync('/reports/:reportId', passport.loggedIn, passport.csrfProtect
});
router.postAsync('/reports-table', passport.loggedIn, async (req, res) => {
return res.json(await reports.listDTAjax(req.context, req.body));
return res.json(await reports.listDTAjax(req.context, null, req.body));
});
router.postAsync('/reports-table/:namespaceId', passport.loggedIn, async (req, res) => {
return res.json(await reports.listDTAjax(req.context, req.params.namespaceId, req.body));
});
router.postAsync('/report-start/:id', passport.loggedIn, passport.csrfProtection, async (req, res) => {

View file

@ -37,11 +37,11 @@ router.deleteAsync('/send-configurations/:sendConfigurationId', passport.loggedI
});
router.postAsync('/send-configurations-table', passport.loggedIn, async (req, res) => {
return res.json(await sendConfigurations.listDTAjax(req.context, req.body));
return res.json(await sendConfigurations.listDTAjax(req.context, null, req.body));
});
router.postAsync('/send-configurations-by-namespace-table/:namespaceId', passport.loggedIn, async (req, res) => {
return res.json(await sendConfigurations.listByNamespaceDTAjax(req.context, castToInteger(req.params.namespaceId), req.body));
router.postAsync('/send-configurations-table/:namespaceId', passport.loggedIn, async (req, res) => {
return res.json(await sendConfigurations.listDTAjax(req.context, castToInteger(req.params.namespaceId), req.body));
});
router.postAsync('/send-configurations-with-send-permission-table', passport.loggedIn, async (req, res) => {

View file

@ -31,11 +31,11 @@ router.deleteAsync('/templates/:templateId', passport.loggedIn, passport.csrfPro
});
router.postAsync('/templates-table', passport.loggedIn, async (req, res) => {
return res.json(await templates.listDTAjax(req.context, req.body));
return res.json(await templates.listDTAjax(req.context, null, req.body));
});
router.postAsync('/templates-by-namespace-table/:namespaceId', passport.loggedIn, async (req, res) => {
return res.json(await templates.listByNamespaceDTAjax(req.context, castToInteger(req.params.namespaceId), req.body));
router.postAsync('/templates-table/:namespaceId', passport.loggedIn, async (req, res) => {
return res.json(await templates.listDTAjax(req.context, castToInteger(req.params.namespaceId), req.body));
});
module.exports = router;

View file

@ -35,7 +35,11 @@ router.postAsync('/users-validate', passport.loggedIn, async (req, res) => {
});
router.postAsync('/users-table', passport.loggedIn, async (req, res) => {
return res.json(await users.listDTAjax(req.context, req.body));
return res.json(await users.listDTAjax(req.context, null, req.body));
});
router.postAsync('/users-table/:namespaceId', passport.loggedIn, async (req, res) => {
return res.json(await users.listDTAjax(req.context, castToInteger(req.params.namespaceId), req.body));
});