mirror of
https://github.com/knadh/listmonk.git
synced 2025-09-26 16:36:59 +08:00
Add permission checks to admin UI to toggle visibility/functionality of components.
This commit is contained in:
parent
dd9612b1ed
commit
474f93559f
20 changed files with 214 additions and 172 deletions
|
@ -14,7 +14,7 @@
|
|||
@toggleGroup="toggleGroup" @doLogout="doLogout" />
|
||||
|
||||
<b-navbar-dropdown v-else>
|
||||
<template v-if="profile" #label>
|
||||
<template v-if="profile.username" #label>
|
||||
<div class="user-avatar">
|
||||
<img v-if="profile.avatar" :src="profile.avatar" alt="" />
|
||||
<span v-else>{{ profile.username[0].toUpperCase() }}</span>
|
||||
|
@ -87,7 +87,6 @@ export default Vue.extend({
|
|||
|
||||
data() {
|
||||
return {
|
||||
profile: null,
|
||||
activeItem: {},
|
||||
activeGroup: {},
|
||||
windowWidth: window.innerWidth,
|
||||
|
@ -155,7 +154,7 @@ export default Vue.extend({
|
|||
},
|
||||
|
||||
computed: {
|
||||
...mapState(['serverConfig']),
|
||||
...mapState(['serverConfig', 'profile']),
|
||||
|
||||
version() {
|
||||
return import.meta.env.VUE_APP_VERSION;
|
||||
|
@ -169,16 +168,15 @@ export default Vue.extend({
|
|||
mounted() {
|
||||
// Lists is required across different views. On app load, fetch the lists
|
||||
// and have them in the store.
|
||||
this.$api.getLists({ minimal: true, per_page: 'all' });
|
||||
if (this.$can('lists:get')) {
|
||||
this.$api.getLists({ minimal: true, per_page: 'all' });
|
||||
}
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
this.windowWidth = window.innerWidth;
|
||||
});
|
||||
|
||||
this.listenEvents();
|
||||
this.$api.getUserProfile().then((d) => {
|
||||
this.profile = d;
|
||||
});
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -480,13 +480,13 @@ export const deleteUser = (id) => http.delete(
|
|||
|
||||
export const getUserProfile = () => http.get(
|
||||
'/api/profile',
|
||||
{ loading: models.users },
|
||||
{ loading: models.users, store: models.profile },
|
||||
);
|
||||
|
||||
export const updateUserProfile = (data) => http.put(
|
||||
'/api/profile',
|
||||
data,
|
||||
{ loading: models.users },
|
||||
{ loading: models.users, store: models.profile },
|
||||
);
|
||||
|
||||
export const getRoles = async () => http.get(
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<b-menu-item :to="{ name: 'dashboard' }" tag="router-link" :active="activeItem.dashboard"
|
||||
icon="view-dashboard-variant-outline" :label="$t('menu.dashboard')" /><!-- dashboard -->
|
||||
|
||||
<b-menu-item :expanded="activeGroup.lists" :active="activeGroup.lists" data-cy="lists"
|
||||
<b-menu-item v-if="$can('lists:get')" :expanded="activeGroup.lists" :active="activeGroup.lists" data-cy="lists"
|
||||
@update:active="(state) => toggleGroup('lists', state)" icon="format-list-bulleted-square"
|
||||
:label="$t('globals.terms.lists')">
|
||||
<b-menu-item :to="{ name: 'lists' }" tag="router-link" :active="activeItem.lists" data-cy="all-lists"
|
||||
|
@ -12,48 +12,54 @@
|
|||
icon="newspaper-variant-outline" :label="$t('menu.forms')" />
|
||||
</b-menu-item><!-- lists -->
|
||||
|
||||
<b-menu-item :expanded="activeGroup.subscribers" :active="activeGroup.subscribers" data-cy="subscribers"
|
||||
@update:active="(state) => toggleGroup('subscribers', state)" icon="account-multiple"
|
||||
<b-menu-item v-if="$can('subscribers:*')" :expanded="activeGroup.subscribers" :active="activeGroup.subscribers"
|
||||
data-cy="subscribers" @update:active="(state) => toggleGroup('subscribers', state)" icon="account-multiple"
|
||||
:label="$t('globals.terms.subscribers')">
|
||||
<b-menu-item :to="{ name: 'subscribers' }" tag="router-link" :active="activeItem.subscribers"
|
||||
data-cy="all-subscribers" icon="account-multiple" :label="$t('menu.allSubscribers')" />
|
||||
<b-menu-item :to="{ name: 'import' }" tag="router-link" :active="activeItem.import" data-cy="import"
|
||||
icon="file-upload-outline" :label="$t('menu.import')" />
|
||||
<b-menu-item :to="{ name: 'bounces' }" tag="router-link" :active="activeItem.bounces" data-cy="bounces"
|
||||
icon="email-bounce" :label="$t('globals.terms.bounces')" />
|
||||
<b-menu-item v-if="$can('subscribers:get')" :to="{ name: 'subscribers' }" tag="router-link"
|
||||
:active="activeItem.subscribers" data-cy="all-subscribers" icon="account-multiple"
|
||||
:label="$t('menu.allSubscribers')" />
|
||||
<b-menu-item v-if="$can('subscribers:import')" :to="{ name: 'import' }" tag="router-link"
|
||||
:active="activeItem.import" data-cy="import" icon="file-upload-outline" :label="$t('menu.import')" />
|
||||
<b-menu-item v-if="$can('bounces:get')" :to="{ name: 'bounces' }" tag="router-link" :active="activeItem.bounces"
|
||||
data-cy="bounces" icon="email-bounce" :label="$t('globals.terms.bounces')" />
|
||||
</b-menu-item><!-- subscribers -->
|
||||
|
||||
<b-menu-item :expanded="activeGroup.campaigns" :active="activeGroup.campaigns" data-cy="campaigns"
|
||||
@update:active="(state) => toggleGroup('campaigns', state)" icon="rocket-launch-outline"
|
||||
<b-menu-item v-if="$can('campaigns:*')" :expanded="activeGroup.campaigns" :active="activeGroup.campaigns"
|
||||
data-cy="campaigns" @update:active="(state) => toggleGroup('campaigns', state)" icon="rocket-launch-outline"
|
||||
:label="$t('globals.terms.campaigns')">
|
||||
<b-menu-item :to="{ name: 'campaigns' }" tag="router-link" :active="activeItem.campaigns" data-cy="all-campaigns"
|
||||
icon="rocket-launch-outline" :label="$t('menu.allCampaigns')" />
|
||||
<b-menu-item :to="{ name: 'campaign', params: { id: 'new' } }" tag="router-link" :active="activeItem.campaign"
|
||||
data-cy="new-campaign" icon="plus" :label="$t('menu.newCampaign')" />
|
||||
<b-menu-item :to="{ name: 'media' }" tag="router-link" :active="activeItem.media" data-cy="media"
|
||||
icon="image-outline" :label="$t('menu.media')" />
|
||||
<b-menu-item :to="{ name: 'templates' }" tag="router-link" :active="activeItem.templates" data-cy="templates"
|
||||
icon="file-image-outline" :label="$t('globals.terms.templates')" />
|
||||
<b-menu-item :to="{ name: 'campaignAnalytics' }" tag="router-link" :active="activeItem.campaignAnalytics"
|
||||
data-cy="analytics" icon="chart-bar" :label="$t('globals.terms.analytics')" />
|
||||
<b-menu-item v-if="$can('campaigns:get')" :to="{ name: 'campaigns' }" tag="router-link"
|
||||
:active="activeItem.campaigns" data-cy="all-campaigns" icon="rocket-launch-outline"
|
||||
:label="$t('menu.allCampaigns')" />
|
||||
<b-menu-item v-if="$can('campaigns:manage')" :to="{ name: 'campaign', params: { id: 'new' } }" tag="router-link"
|
||||
:active="activeItem.campaign" data-cy="new-campaign" icon="plus" :label="$t('menu.newCampaign')" />
|
||||
<b-menu-item v-if="$can('media:*')" :to="{ name: 'media' }" tag="router-link" :active="activeItem.media"
|
||||
data-cy="media" icon="image-outline" :label="$t('menu.media')" />
|
||||
<b-menu-item v-if="$can('templates:get')" :to="{ name: 'templates' }" tag="router-link"
|
||||
:active="activeItem.templates" data-cy="templates" icon="file-image-outline"
|
||||
:label="$t('globals.terms.templates')" />
|
||||
<b-menu-item v-if="$can('campaigns:get_analytics')" :to="{ name: 'campaignAnalytics' }" tag="router-link"
|
||||
:active="activeItem.campaignAnalytics" data-cy="analytics" icon="chart-bar"
|
||||
:label="$t('globals.terms.analytics')" />
|
||||
</b-menu-item><!-- campaigns -->
|
||||
|
||||
<b-menu-item :expanded="activeGroup.users" :active="activeGroup.users" data-cy="users"
|
||||
@update:active="(state) => toggleGroup('users', state)" icon="account-multiple" :label="$t('globals.terms.users')">
|
||||
<b-menu-item :to="{ name: 'users' }" tag="router-link" :active="activeItem.users" data-cy="users"
|
||||
icon="account-multiple" :label="$t('globals.terms.users')" />
|
||||
<b-menu-item :to="{ name: 'roles' }" tag="router-link" :active="activeItem.roles" data-cy="roles"
|
||||
icon="newspaper-variant-outline" :label="$t('users.roles')" />
|
||||
<b-menu-item v-if="$can('users:*') || $can('roles:*')" :expanded="activeGroup.users" :active="activeGroup.users"
|
||||
data-cy="users" @update:active="(state) => toggleGroup('users', state)" icon="account-multiple"
|
||||
:label="$t('globals.terms.users')">
|
||||
<b-menu-item v-if="$can('users:get')" :to="{ name: 'users' }" tag="router-link" :active="activeItem.users"
|
||||
data-cy="users" icon="account-multiple" :label="$t('globals.terms.users')" />
|
||||
<b-menu-item v-if="$can('roles:get')" :to="{ name: 'roles' }" tag="router-link" :active="activeItem.roles"
|
||||
data-cy="roles" icon="newspaper-variant-outline" :label="$t('users.roles')" />
|
||||
</b-menu-item>
|
||||
|
||||
<b-menu-item :expanded="activeGroup.settings" :active="activeGroup.settings" data-cy="settings"
|
||||
@update:active="(state) => toggleGroup('settings', state)" icon="cog-outline" :label="$t('menu.settings')">
|
||||
<b-menu-item :to="{ name: 'settings' }" tag="router-link" :active="activeItem.settings" data-cy="all-settings"
|
||||
icon="cog-outline" :label="$t('menu.settings')" />
|
||||
<b-menu-item :to="{ name: 'maintenance' }" tag="router-link" :active="activeItem.maintenance"
|
||||
data-cy="maintenance" icon="wrench-outline" :label="$t('menu.maintenance')" />
|
||||
<b-menu-item :to="{ name: 'logs' }" tag="router-link" :active="activeItem.logs" data-cy="logs"
|
||||
icon="newspaper-variant-outline" :label="$t('menu.logs')" />
|
||||
<b-menu-item v-if="$can('settings:*')" :expanded="activeGroup.settings" :active="activeGroup.settings"
|
||||
data-cy="settings" @update:active="(state) => toggleGroup('settings', state)" icon="cog-outline"
|
||||
:label="$t('menu.settings')">
|
||||
<b-menu-item v-if="$can('settings:get')" :to="{ name: 'settings' }" tag="router-link"
|
||||
:active="activeItem.settings" data-cy="all-settings" icon="cog-outline" :label="$t('menu.settings')" />
|
||||
<b-menu-item v-if="$can('settings:maintain')" :to="{ name: 'maintenance' }" tag="router-link"
|
||||
:active="activeItem.maintenance" data-cy="maintenance" icon="wrench-outline" :label="$t('menu.maintenance')" />
|
||||
<b-menu-item v-if="$can('settings:get')" :to="{ name: 'logs' }" tag="router-link" :active="activeItem.logs"
|
||||
data-cy="logs" icon="newspaper-variant-outline" :label="$t('menu.logs')" />
|
||||
</b-menu-item><!-- settings -->
|
||||
|
||||
<b-menu-item v-if="isMobile" icon="logout-variant" :label="$t('users.logout')" @click.prevent="doLogout" />
|
||||
|
@ -61,6 +67,8 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: 'Navigation',
|
||||
|
||||
|
@ -80,6 +88,10 @@ export default {
|
|||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState(['profile']),
|
||||
},
|
||||
|
||||
mounted() {
|
||||
// A hack to close the open accordion burger menu items on click.
|
||||
// Buefy does not have a way to do this.
|
||||
|
|
|
@ -9,6 +9,7 @@ export const models = Object.freeze({
|
|||
media: 'media',
|
||||
bounces: 'bounces',
|
||||
users: 'users',
|
||||
profile: 'profile',
|
||||
roles: 'roles',
|
||||
settings: 'settings',
|
||||
logs: 'logs',
|
||||
|
|
|
@ -31,28 +31,43 @@ router.afterEach((to) => {
|
|||
});
|
||||
});
|
||||
|
||||
function initConfig(app) {
|
||||
// Load server side config and language before mounting the app.
|
||||
api.getServerConfig().then((data) => {
|
||||
api.getLang(data.lang).then((lang) => {
|
||||
i18n.locale = data.lang;
|
||||
i18n.setLocaleMessage(i18n.locale, lang);
|
||||
async function initConfig(app) {
|
||||
// Load logged in user profile, server side config, and the language file before mounting the app.
|
||||
const [profile, cfg] = await Promise.all([api.getUserProfile(), api.getServerConfig()]);
|
||||
|
||||
Vue.prototype.$utils = new Utils(i18n);
|
||||
Vue.prototype.$api = api;
|
||||
const lang = await api.getLang(cfg.lang);
|
||||
i18n.locale = cfg.lang;
|
||||
i18n.setLocaleMessage(i18n.locale, lang);
|
||||
|
||||
// Set the page title after i18n has loaded.
|
||||
const to = router.history.current;
|
||||
const t = to.meta.title ? `${i18n.tc(to.meta.title, 0)} /` : '';
|
||||
document.title = `${t} listmonk`;
|
||||
Vue.prototype.$utils = new Utils(i18n);
|
||||
Vue.prototype.$api = api;
|
||||
|
||||
if (app) {
|
||||
app.$mount('#app');
|
||||
}
|
||||
});
|
||||
});
|
||||
// $can('permission:name') is used in the UI to chekc whether the logged in user
|
||||
// has a certain permission to toggle visibility of UI objects and UI functionality.
|
||||
Vue.prototype.$can = (perm) => {
|
||||
if (profile.role_id === 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
api.getSettings();
|
||||
// If the perm ends with a wildcard, check whether at least one permission
|
||||
// in the group is present. Eg: campaigns:* will return true if at least
|
||||
// one of campaigns:get, campaigns:manage etc. are present.
|
||||
if (perm.endsWith('*')) {
|
||||
const group = `${perm.split(':')[0]}:`;
|
||||
return profile.permissions.some((p) => p.startsWith(group));
|
||||
}
|
||||
|
||||
return profile.permissions.includes(perm);
|
||||
};
|
||||
|
||||
// Set the page title after i18n has loaded.
|
||||
const to = router.history.current;
|
||||
const title = to.meta.title ? `${i18n.tc(to.meta.title, 0)} /` : '';
|
||||
document.title = `${title} listmonk`;
|
||||
|
||||
if (app) {
|
||||
app.$mount('#app');
|
||||
}
|
||||
}
|
||||
|
||||
const v = new Vue({
|
||||
|
|
|
@ -42,6 +42,7 @@ export default new Vuex.Store({
|
|||
[models.media]: (state) => state[models.media],
|
||||
[models.templates]: (state) => state[models.templates],
|
||||
[models.users]: (state) => state[models.users],
|
||||
[models.profile]: (state) => state[models.profile],
|
||||
[models.roles]: (state) => state[models.roles],
|
||||
[models.settings]: (state) => state[models.settings],
|
||||
[models.serverConfig]: (state) => state[models.serverConfig],
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
</div>
|
||||
|
||||
<div class="column is-6">
|
||||
<div class="buttons">
|
||||
<div v-if="$can('campaigns:manage')" class="buttons">
|
||||
<b-field grouped v-if="isEditing && canEdit">
|
||||
<b-field expanded>
|
||||
<b-button expanded @click="() => onSubmit('update')" :loading="loading.campaigns" type="is-primary"
|
||||
|
@ -113,7 +113,8 @@
|
|||
:message="form.sendAtDate ? $utils.duration(Date(), form.sendAtDate) : ''">
|
||||
<b-datetimepicker v-model="form.sendAtDate" :disabled="!canEdit"
|
||||
:placeholder="$t('campaigns.dateAndTime')" icon="calendar-clock"
|
||||
:timepicker="{ hourFormat: '24' }" :datetime-formatter="formatDateTime" horizontal-time-picker />
|
||||
:timepicker="{ hourFormat: '24' }" :datetime-formatter="formatDateTime"
|
||||
horizontal-time-picker />
|
||||
</b-field>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -140,7 +141,7 @@
|
|||
</b-field>
|
||||
</form>
|
||||
</div>
|
||||
<div class="column is-4 is-offset-1">
|
||||
<div v-if="$can('campaigns:manage')" class="column is-4 is-offset-1">
|
||||
<br />
|
||||
<div class="box">
|
||||
<h3 class="title is-size-6">
|
||||
|
@ -175,14 +176,15 @@
|
|||
</a>
|
||||
</p>
|
||||
|
||||
<b-field v-if="isAttachFieldVisible" :label="$t('campaigns.attachments')" label-position="on-border" expanded
|
||||
data-cy="media">
|
||||
<b-field v-if="isAttachFieldVisible" :label="$t('campaigns.attachments')" label-position="on-border"
|
||||
expanded data-cy="media">
|
||||
<b-taginput v-model="form.media" name="media" ellipsis icon="tag-outline" ref="media" field="filename"
|
||||
@focus="onOpenAttach" :disabled="!canEdit" />
|
||||
</b-field>
|
||||
</div>
|
||||
<div class="column has-text-right">
|
||||
<a href="https://listmonk.app/docs/templating/#template-expressions" target="_blank" rel="noopener noreferer">
|
||||
<a href="https://listmonk.app/docs/templating/#template-expressions" target="_blank"
|
||||
rel="noopener noreferer">
|
||||
<b-icon icon="code" /> {{ $t('campaigns.templatingRef') }}</a>
|
||||
<span v-if="canEdit && form.content.contentType !== 'plain'" class="is-size-6 has-text-grey ml-6">
|
||||
<a v-if="form.altbody === null" href="#" @click.prevent="onAddAltBody">
|
||||
|
@ -212,8 +214,9 @@
|
|||
<b-switch data-cy="btn-archive" v-model="form.archive" :disabled="!canArchive" />
|
||||
</div>
|
||||
<div class="column is-12">
|
||||
<a :href="`${settings['app.root_url']}/archive/${data.uuid}`" target="_blank" rel="noopener noreferer"
|
||||
:class="{ 'has-text-grey-light': !form.archive }" aria-label="$t('campaigns.archive')">
|
||||
<a :href="`${settings['app.root_url']}/archive/${data.uuid}`" target="_blank"
|
||||
rel="noopener noreferer" :class="{ 'has-text-grey-light': !form.archive }"
|
||||
aria-label="$t('campaigns.archive')">
|
||||
<b-icon icon="link-variant" />
|
||||
</a>
|
||||
</div>
|
||||
|
@ -245,8 +248,8 @@
|
|||
</div>
|
||||
|
||||
<div class="column has-text-right">
|
||||
<a v-if="!this.form.archiveMetaStr || this.form.archiveMetaStr === '{}'" class="button is-primary" href="#"
|
||||
@click.prevent="onFillArchiveMeta" aria-label="{}"><b-icon icon="code" /></a>
|
||||
<a v-if="!this.form.archiveMetaStr || this.form.archiveMetaStr === '{}'" class="button is-primary"
|
||||
href="#" @click.prevent="onFillArchiveMeta" aria-label="{}"><b-icon icon="code" /></a>
|
||||
</div>
|
||||
</div>
|
||||
<b-field>
|
||||
|
@ -596,7 +599,7 @@ export default Vue.extend({
|
|||
},
|
||||
|
||||
computed: {
|
||||
...mapState(['settings', 'loading', 'lists', 'templates']),
|
||||
...mapState(['serverConfig', 'loading', 'lists', 'templates']),
|
||||
|
||||
canEdit() {
|
||||
return this.isNew
|
||||
|
@ -624,7 +627,7 @@ export default Vue.extend({
|
|||
},
|
||||
|
||||
messengers() {
|
||||
return ['email', ...this.settings.messengers.map((m) => m.name)];
|
||||
return ['email', ...this.serverConfig.messengers.map((m) => m.name)];
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
</h1>
|
||||
</div>
|
||||
<div class="column has-text-right">
|
||||
<b-field expanded>
|
||||
<b-field v-if="$can('campaigns:manage')" expanded>
|
||||
<b-button expanded :to="{ name: 'campaign', params: { id: 'new' } }" tag="router-link" class="btn-new"
|
||||
type="is-primary" icon-left="plus" data-cy="btn-new">
|
||||
{{ $t('globals.buttons.new') }}
|
||||
|
@ -170,51 +170,56 @@
|
|||
<b-table-column v-slot="props" cell-class="actions" width="15%" align="right">
|
||||
<div>
|
||||
<!-- start / pause / resume / scheduled -->
|
||||
<a v-if="canStart(props.row)" href="#"
|
||||
@click.prevent="$utils.confirm(null, () => changeCampaignStatus(props.row, 'running'))" data-cy="btn-start"
|
||||
:aria-label="$t('campaigns.start')">
|
||||
<b-tooltip :label="$t('campaigns.start')" type="is-dark">
|
||||
<b-icon icon="rocket-launch-outline" size="is-small" />
|
||||
</b-tooltip>
|
||||
</a>
|
||||
<a v-if="canPause(props.row)" href="#"
|
||||
@click.prevent="$utils.confirm(null, () => changeCampaignStatus(props.row, 'paused'))" data-cy="btn-pause"
|
||||
:aria-label="$t('campaigns.pause')">
|
||||
<b-tooltip :label="$t('campaigns.pause')" type="is-dark">
|
||||
<b-icon icon="pause-circle-outline" size="is-small" />
|
||||
</b-tooltip>
|
||||
</a>
|
||||
<a v-if="canResume(props.row)" href="#"
|
||||
@click.prevent="$utils.confirm(null, () => changeCampaignStatus(props.row, 'running'))" data-cy="btn-resume"
|
||||
:aria-label="$t('campaigns.send')">
|
||||
<b-tooltip :label="$t('campaigns.send')" type="is-dark">
|
||||
<b-icon icon="rocket-launch-outline" size="is-small" />
|
||||
</b-tooltip>
|
||||
</a>
|
||||
<a v-if="canSchedule(props.row)" href="#"
|
||||
@click.prevent="$utils.confirm($t('campaigns.confirmSchedule'), () => changeCampaignStatus(props.row, 'scheduled'))"
|
||||
data-cy="btn-schedule" :aria-label="$t('campaigns.schedule')">
|
||||
<b-tooltip :label="$t('campaigns.schedule')" type="is-dark">
|
||||
<b-icon icon="clock-start" size="is-small" />
|
||||
</b-tooltip>
|
||||
</a>
|
||||
<template v-if="$can('campaigns:manage')">
|
||||
<a v-if="canStart(props.row)" href="#"
|
||||
@click.prevent="$utils.confirm(null, () => changeCampaignStatus(props.row, 'running'))"
|
||||
data-cy="btn-start" :aria-label="$t('campaigns.start')">
|
||||
<b-tooltip :label="$t('campaigns.start')" type="is-dark">
|
||||
<b-icon icon="rocket-launch-outline" size="is-small" />
|
||||
</b-tooltip>
|
||||
</a>
|
||||
|
||||
<!-- placeholder for finished campaigns -->
|
||||
<a v-if="!canCancel(props.row) && !canSchedule(props.row) && !canStart(props.row)" href="#" data-disabled
|
||||
aria-label=" ">
|
||||
<b-icon icon="rocket-launch-outline" size="is-small" />
|
||||
</a>
|
||||
<a v-if="canPause(props.row)" href="#"
|
||||
@click.prevent="$utils.confirm(null, () => changeCampaignStatus(props.row, 'paused'))" data-cy="btn-pause"
|
||||
:aria-label="$t('campaigns.pause')">
|
||||
<b-tooltip :label="$t('campaigns.pause')" type="is-dark">
|
||||
<b-icon icon="pause-circle-outline" size="is-small" />
|
||||
</b-tooltip>
|
||||
</a>
|
||||
|
||||
<a v-if="canCancel(props.row)" href="#"
|
||||
@click.prevent="$utils.confirm(null, () => changeCampaignStatus(props.row, 'cancelled'))"
|
||||
data-cy="btn-cancel" :aria-label="$t('globals.buttons.cancel')">
|
||||
<b-tooltip :label="$t('globals.buttons.cancel')" type="is-dark">
|
||||
<a v-if="canResume(props.row)" href="#"
|
||||
@click.prevent="$utils.confirm(null, () => changeCampaignStatus(props.row, 'running'))"
|
||||
data-cy="btn-resume" :aria-label="$t('campaigns.send')">
|
||||
<b-tooltip :label="$t('campaigns.send')" type="is-dark">
|
||||
<b-icon icon="rocket-launch-outline" size="is-small" />
|
||||
</b-tooltip>
|
||||
</a>
|
||||
|
||||
<a v-if="canSchedule(props.row)" href="#"
|
||||
@click.prevent="$utils.confirm($t('campaigns.confirmSchedule'), () => changeCampaignStatus(props.row, 'scheduled'))"
|
||||
data-cy="btn-schedule" :aria-label="$t('campaigns.schedule')">
|
||||
<b-tooltip :label="$t('campaigns.schedule')" type="is-dark">
|
||||
<b-icon icon="clock-start" size="is-small" />
|
||||
</b-tooltip>
|
||||
</a>
|
||||
|
||||
<!-- placeholder for finished campaigns -->
|
||||
<a v-if="!canCancel(props.row) && !canSchedule(props.row) && !canStart(props.row)" href="#" data-disabled
|
||||
aria-label=" ">
|
||||
<b-icon icon="rocket-launch-outline" size="is-small" />
|
||||
</a>
|
||||
|
||||
<a v-if="canCancel(props.row)" href="#"
|
||||
@click.prevent="$utils.confirm(null, () => changeCampaignStatus(props.row, 'cancelled'))"
|
||||
data-cy="btn-cancel" :aria-label="$t('globals.buttons.cancel')">
|
||||
<b-tooltip :label="$t('globals.buttons.cancel')" type="is-dark">
|
||||
<b-icon icon="cancel" size="is-small" />
|
||||
</b-tooltip>
|
||||
</a>
|
||||
<a v-else href="#" data-disabled aria-label=" ">
|
||||
<b-icon icon="cancel" size="is-small" />
|
||||
</b-tooltip>
|
||||
</a>
|
||||
<a v-else href="#" data-disabled aria-label=" ">
|
||||
<b-icon icon="cancel" size="is-small" />
|
||||
</a>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<a href="#" @click.prevent="previewCampaign(props.row)" data-cy="btn-preview"
|
||||
:aria-label="$t('campaigns.preview')">
|
||||
|
@ -222,7 +227,7 @@
|
|||
<b-icon icon="file-find-outline" size="is-small" />
|
||||
</b-tooltip>
|
||||
</a>
|
||||
<a href="#" @click.prevent="$utils.prompt($t('globals.buttons.clone'),
|
||||
<a v-if="$can('campaigns:manage')" href="#" @click.prevent="$utils.prompt($t('globals.buttons.clone'),
|
||||
{
|
||||
placeholder: $t('globals.fields.name'),
|
||||
value: $t('campaigns.copyOf', { name: props.row.name }),
|
||||
|
@ -232,12 +237,13 @@
|
|||
<b-icon icon="file-multiple-outline" size="is-small" />
|
||||
</b-tooltip>
|
||||
</a>
|
||||
<router-link :to="{ name: 'campaignAnalytics', query: { id: props.row.id } }">
|
||||
<router-link v-if="$can('campaigns:get_analytics')"
|
||||
:to="{ name: 'campaignAnalytics', query: { id: props.row.id } }">
|
||||
<b-tooltip :label="$t('globals.terms.analytics')" type="is-dark">
|
||||
<b-icon icon="chart-bar" size="is-small" />
|
||||
</b-tooltip>
|
||||
</router-link>
|
||||
<a href="#"
|
||||
<a v-if="$can('campaigns:manage')" href="#"
|
||||
@click.prevent="$utils.confirm($t('campaigns.confirmDelete', { name: props.row.name }), () => deleteCampaign(props.row))"
|
||||
data-cy="btn-delete" :aria-label="$t('globals.buttons.delete')">
|
||||
<b-icon icon="trash-can-outline" size="is-small" />
|
||||
|
|
|
@ -58,7 +58,8 @@
|
|||
<b-button @click="$parent.close()">
|
||||
{{ $t('globals.buttons.close') }}
|
||||
</b-button>
|
||||
<b-button native-type="submit" type="is-primary" :loading="loading.lists" data-cy="btn-save">
|
||||
<b-button v-if="$can('lists:manage')" native-type="submit" type="is-primary" :loading="loading.lists"
|
||||
data-cy="btn-save">
|
||||
{{ $t('globals.buttons.save') }}
|
||||
</b-button>
|
||||
</footer>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
</h1>
|
||||
</div>
|
||||
<div class="column has-text-right">
|
||||
<b-field expanded>
|
||||
<b-field v-if="$can('lists:manage')" expanded>
|
||||
<b-button expanded type="is-primary" icon-left="plus" class="btn-new" @click="showNewForm" data-cy="btn-new">
|
||||
{{ $t('globals.buttons.new') }}
|
||||
</b-button>
|
||||
|
@ -25,7 +25,8 @@
|
|||
<form @submit.prevent="getLists">
|
||||
<div>
|
||||
<b-field>
|
||||
<b-input v-model="queryParams.query" name="query" expanded icon="magnify" ref="query" data-cy="query" />
|
||||
<b-input v-model="queryParams.query" name="query" expanded icon="magnify" ref="query"
|
||||
data-cy="query" />
|
||||
<p class="controls">
|
||||
<b-button native-type="submit" type="is-primary" icon-left="magnify" data-cy="btn-query" />
|
||||
</p>
|
||||
|
@ -106,26 +107,28 @@
|
|||
|
||||
<b-table-column v-slot="props" cell-class="actions" align="right">
|
||||
<div>
|
||||
<router-link :to="`/campaigns/new?list_id=${props.row.id}`" data-cy="btn-campaign">
|
||||
<router-link v-if="$can('campaigns:manage')" :to="`/campaigns/new?list_id=${props.row.id}`"
|
||||
data-cy="btn-campaign">
|
||||
<b-tooltip :label="$t('lists.sendCampaign')" type="is-dark">
|
||||
<b-icon icon="rocket-launch-outline" size="is-small" />
|
||||
</b-tooltip>
|
||||
</router-link>
|
||||
|
||||
<a href="#" @click.prevent="showEditForm(props.row)" data-cy="btn-edit"
|
||||
<a v-if="$can('lists:manage')" href="#" @click.prevent="showEditForm(props.row)" data-cy="btn-edit"
|
||||
:aria-label="$t('globals.buttons.edit')">
|
||||
<b-tooltip :label="$t('globals.buttons.edit')" type="is-dark">
|
||||
<b-icon icon="pencil-outline" size="is-small" />
|
||||
</b-tooltip>
|
||||
</a>
|
||||
|
||||
<router-link :to="{ name: 'import', query: { list_id: props.row.id } }" data-cy="btn-import">
|
||||
<router-link v-if="$can('lists:import')" :to="{ name: 'import', query: { list_id: props.row.id } }"
|
||||
data-cy="btn-import">
|
||||
<b-tooltip :label="$t('import.title')" type="is-dark">
|
||||
<b-icon icon="file-upload-outline" size="is-small" />
|
||||
</b-tooltip>
|
||||
</router-link>
|
||||
|
||||
<a href="#" @click.prevent="deleteList(props.row)" data-cy="btn-delete"
|
||||
<a v-if="$can('lists:manage')" href="#" @click.prevent="deleteList(props.row)" data-cy="btn-delete"
|
||||
:aria-label="$t('globals.buttons.delete')">
|
||||
<b-tooltip :label="$t('globals.buttons.delete')" type="is-dark">
|
||||
<b-icon icon="trash-can-outline" size="is-small" />
|
||||
|
|
|
@ -141,7 +141,7 @@ export default Vue.extend({
|
|||
}, {});
|
||||
|
||||
// It's the superadmin role. Disable the form.
|
||||
if (this.$props.data.id === 1) {
|
||||
if (this.$props.data.id === 1 || !this.$can('roles:manage')) {
|
||||
this.disabled = true;
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
</h1>
|
||||
</div>
|
||||
<div class="column has-text-right">
|
||||
<b-field expanded>
|
||||
<b-field v-if="$can('users:manage')" expanded>
|
||||
<b-button expanded type="is-primary" icon-left="plus" class="btn-new" @click="showNewForm" data-cy="btn-new">
|
||||
{{ $t('globals.buttons.new') }}
|
||||
</b-button>
|
||||
|
@ -37,31 +37,33 @@
|
|||
</b-table-column>
|
||||
|
||||
<b-table-column v-slot="props" cell-class="actions" align="right">
|
||||
<a href="#" @click.prevent="$utils.prompt($t('globals.buttons.clone'),
|
||||
{
|
||||
placeholder: $t('globals.fields.name'),
|
||||
value: $t('campaigns.copyOf', { name: props.row.name }),
|
||||
},
|
||||
(name) => onCloneRole(name, props.row))" data-cy="btn-clone" :aria-label="$t('globals.buttons.clone')">
|
||||
<b-tooltip :label="$t('globals.buttons.clone')" type="is-dark">
|
||||
<b-icon icon="file-multiple-outline" size="is-small" />
|
||||
</b-tooltip>
|
||||
</a>
|
||||
|
||||
<template v-if="props.row.id !== 1">
|
||||
<a href="#" @click.prevent="showEditForm(props.row)" data-cy="btn-edit"
|
||||
:aria-label="$t('globals.buttons.edit')">
|
||||
<b-tooltip :label="$t('globals.buttons.edit')" type="is-dark">
|
||||
<b-icon icon="pencil-outline" size="is-small" />
|
||||
<template v-if="$can('roles:manage')">
|
||||
<a href="#" @click.prevent="$utils.prompt($t('globals.buttons.clone'),
|
||||
{
|
||||
placeholder: $t('globals.fields.name'),
|
||||
value: $t('campaigns.copyOf', { name: props.row.name }),
|
||||
},
|
||||
(name) => onCloneRole(name, props.row))" data-cy="btn-clone" :aria-label="$t('globals.buttons.clone')">
|
||||
<b-tooltip :label="$t('globals.buttons.clone')" type="is-dark">
|
||||
<b-icon icon="file-multiple-outline" size="is-small" />
|
||||
</b-tooltip>
|
||||
</a>
|
||||
|
||||
<a href="#" @click.prevent="onDeleteRole(props.row)" data-cy="btn-delete"
|
||||
:aria-label="$t('globals.buttons.delete')">
|
||||
<b-tooltip :label="$t('globals.buttons.delete')" type="is-dark">
|
||||
<b-icon icon="trash-can-outline" size="is-small" />
|
||||
</b-tooltip>
|
||||
</a>
|
||||
<template v-if="props.row.id !== 1">
|
||||
<a href="#" @click.prevent="showEditForm(props.row)" data-cy="btn-edit"
|
||||
:aria-label="$t('globals.buttons.edit')">
|
||||
<b-tooltip :label="$t('globals.buttons.edit')" type="is-dark">
|
||||
<b-icon icon="pencil-outline" size="is-small" />
|
||||
</b-tooltip>
|
||||
</a>
|
||||
|
||||
<a href="#" @click.prevent="onDeleteRole(props.row)" data-cy="btn-delete"
|
||||
:aria-label="$t('globals.buttons.delete')">
|
||||
<b-tooltip :label="$t('globals.buttons.delete')" type="is-dark">
|
||||
<b-icon icon="trash-can-outline" size="is-small" />
|
||||
</b-tooltip>
|
||||
</a>
|
||||
</template>
|
||||
</template>
|
||||
</b-table-column>
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
</h1>
|
||||
</div>
|
||||
<div class="column has-text-right">
|
||||
<b-field expanded>
|
||||
<b-field v-if="$can('settings:manage')" expanded>
|
||||
<b-button expanded :disabled="!hasFormChanged" type="is-primary" icon-left="content-save-outline"
|
||||
native-type="submit" class="isSaveEnabled" data-cy="btn-save">
|
||||
{{ $t('globals.buttons.save') }}
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
</b-checkbox>
|
||||
</b-field>
|
||||
</div>
|
||||
<div class="column is-5 has-text-right" v-if="isEditing">
|
||||
<div v-if="$can('subscribers:manage') && isEditing" class="column is-5 has-text-right">
|
||||
<a href="#" @click.prevent="sendOptinConfirmation" :class="{ 'is-disabled': !hasOptinList }">
|
||||
<b-icon icon="email-outline" size="is-small" />
|
||||
{{ $t('subscribers.sendOptinConfirm') }}</a>
|
||||
|
@ -147,7 +147,8 @@
|
|||
<b-button @click="$parent.close()">
|
||||
{{ $t('globals.buttons.close') }}
|
||||
</b-button>
|
||||
<b-button native-type="submit" type="is-primary" :loading="loading.subscribers">
|
||||
<b-button v-if="$can('subscribers:manage')" native-type="submit" type="is-primary"
|
||||
:loading="loading.subscribers">
|
||||
{{ $t('globals.buttons.save') }}
|
||||
</b-button>
|
||||
</footer>
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
</h1>
|
||||
</div>
|
||||
<div class="column has-text-right">
|
||||
<b-field expanded>
|
||||
<b-field v-if="$can('subscribers:manage')" expanded>
|
||||
<b-button expanded type="is-primary" icon-left="plus" @click="showNewForm" data-cy="btn-new" class="btn-new">
|
||||
{{ $t('globals.buttons.new') }}
|
||||
</b-button>
|
||||
|
@ -42,7 +42,8 @@
|
|||
data-cy="query" />
|
||||
<span class="is-size-6 has-text-grey">
|
||||
{{ $t('subscribers.advancedQueryHelp') }}.{{ ' ' }}
|
||||
<a href="https://listmonk.app/docs/querying-and-segmentation" target="_blank" rel="noopener noreferrer">
|
||||
<a href="https://listmonk.app/docs/querying-and-segmentation" target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
{{ $t('globals.buttons.learnMore') }}.
|
||||
</a>
|
||||
</span>
|
||||
|
@ -69,8 +70,8 @@
|
|||
</section><!-- control -->
|
||||
|
||||
<br />
|
||||
<b-table :data="subscribers.results ?? []" :loading="loading.subscribers" @check-all="onTableCheck" @check="onTableCheck"
|
||||
:checked-rows.sync="bulk.checked" paginated backend-pagination pagination-position="both"
|
||||
<b-table :data="subscribers.results ?? []" :loading="loading.subscribers" @check-all="onTableCheck"
|
||||
@check="onTableCheck" :checked-rows.sync="bulk.checked" paginated backend-pagination pagination-position="both"
|
||||
@page-change="onPageChange" :current-page="queryParams.page" :per-page="subscribers.perPage"
|
||||
:total="subscribers.total" hoverable checkable backend-sorting @sort="onSort">
|
||||
<template #top-left>
|
||||
|
@ -157,14 +158,14 @@
|
|||
<b-icon icon="cloud-download-outline" size="is-small" />
|
||||
</b-tooltip>
|
||||
</a>
|
||||
<a :href="`/subscribers/${props.row.id}`" @click.prevent="showEditForm(props.row)" data-cy="btn-edit"
|
||||
:aria-label="$t('globals.buttons.edit')">
|
||||
<a v-if="$can('subscribers:manage')" :href="`/subscribers/${props.row.id}`"
|
||||
@click.prevent="showEditForm(props.row)" data-cy="btn-edit" :aria-label="$t('globals.buttons.edit')">
|
||||
<b-tooltip :label="$t('globals.buttons.edit')" type="is-dark">
|
||||
<b-icon icon="pencil-outline" size="is-small" />
|
||||
</b-tooltip>
|
||||
</a>
|
||||
<a href="#" @click.prevent="deleteSubscriber(props.row)" data-cy="btn-delete"
|
||||
:aria-label="$t('globals.buttons.delete')">
|
||||
<a v-if="$can('subscribers:manage')" href="#" @click.prevent="deleteSubscriber(props.row)"
|
||||
data-cy="btn-delete" :aria-label="$t('globals.buttons.delete')">
|
||||
<b-tooltip :label="$t('globals.buttons.delete')" type="is-dark">
|
||||
<b-icon icon="trash-can-outline" size="is-small" />
|
||||
</b-tooltip>
|
||||
|
|
|
@ -93,7 +93,8 @@
|
|||
<b-button @click="$parent.close()">
|
||||
{{ $t('globals.buttons.close') }}
|
||||
</b-button>
|
||||
<b-button v-if="!apiToken" native-type="submit" type="is-primary" :loading="loading.lists" data-cy="btn-save">
|
||||
<b-button v-if="$can('users:manage') && !apiToken" native-type="submit" type="is-primary"
|
||||
:loading="loading.lists" data-cy="btn-save">
|
||||
{{ $t('globals.buttons.save') }}
|
||||
</b-button>
|
||||
</footer>
|
||||
|
|
|
@ -6,9 +6,7 @@
|
|||
@{{ form.username }}
|
||||
</h1>
|
||||
|
||||
<b-tag :class="{ [form.type]: form.status === 'enabled' }">
|
||||
{{ $t(`users.type.${form.type}`) }}
|
||||
</b-tag>
|
||||
<b-tag>{{ form.roleName }}</b-tag>
|
||||
|
||||
<br /><br /><br />
|
||||
<form @submit.prevent="onSubmit">
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
</h1>
|
||||
</div>
|
||||
<div class="column has-text-right">
|
||||
<b-field expanded>
|
||||
<b-field v-if="$can('users:manage')" expanded>
|
||||
<b-button expanded type="is-primary" icon-left="plus" class="btn-new" @click="showNewForm" data-cy="btn-new">
|
||||
{{ $t('globals.buttons.new') }}
|
||||
</b-button>
|
||||
|
@ -91,14 +91,14 @@
|
|||
|
||||
<b-table-column v-slot="props" cell-class="actions" align="right">
|
||||
<div>
|
||||
<a href="#" @click.prevent="showEditForm(props.row)" data-cy="btn-edit"
|
||||
<a v-if="$can('users:manage')" href="#" @click.prevent="showEditForm(props.row)" data-cy="btn-edit"
|
||||
:aria-label="$t('globals.buttons.edit')">
|
||||
<b-tooltip :label="$t('globals.buttons.edit')" type="is-dark">
|
||||
<b-icon icon="pencil-outline" size="is-small" />
|
||||
</b-tooltip>
|
||||
</a>
|
||||
|
||||
<a href="#" @click.prevent="deleteUser(props.row)" data-cy="btn-delete"
|
||||
<a v-if="$can('users:manage')" href="#" @click.prevent="deleteUser(props.row)" data-cy="btn-delete"
|
||||
:aria-label="$t('globals.buttons.delete')">
|
||||
<b-tooltip :label="$t('globals.buttons.delete')" type="is-dark">
|
||||
<b-icon icon="trash-can-outline" size="is-small" />
|
||||
|
|
|
@ -207,7 +207,6 @@
|
|||
"globals.months.9": "Sep",
|
||||
"globals.states.off": "Off",
|
||||
"globals.terms.all": "All",
|
||||
"globals.terms.admin": "Admin",
|
||||
"globals.terms.analytics": "Analytics",
|
||||
"globals.terms.bounce": "Bounce | Bounces",
|
||||
"globals.terms.bounces": "Bounces",
|
||||
|
|
|
@ -63,12 +63,12 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"group": "admin",
|
||||
"group": "settings",
|
||||
"permissions":
|
||||
[
|
||||
"settings:get",
|
||||
"settings:manage",
|
||||
"maintenance:manage"
|
||||
"settings:maintain"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
Loading…
Add table
Reference in a new issue