diff --git a/dashboard/index.html b/dashboard/index.html
index 8316698cd..e6c9f9f56 100644
--- a/dashboard/index.html
+++ b/dashboard/index.html
@@ -150,6 +150,7 @@
+
diff --git a/dashboard/public/js/index.js b/dashboard/public/js/index.js
index 00122fb17..3466f73dc 100644
--- a/dashboard/public/js/index.js
+++ b/dashboard/public/js/index.js
@@ -88,6 +88,9 @@ app.config(['$routeProvider', function ($routeProvider) {
}).when('/emails-mailboxes', {
// controller: 'EmailsEventlogController',
// templateUrl: 'views/emails-eventlog.html?' + window.VITE_CACHE_ID
+ }).when('/emails-mailinglists', {
+ // controller: 'EmailsEventlogController',
+ // templateUrl: 'views/emails-eventlog.html?' + window.VITE_CACHE_ID
}).when('/emails-queue', {
// controller: 'EmailsQueueController',
// templateUrl: 'views/emails-queue.html?' + window.VITE_CACHE_ID
diff --git a/dashboard/src/Index.vue b/dashboard/src/Index.vue
index 49fee5fb7..8fa7f69fd 100644
--- a/dashboard/src/Index.vue
+++ b/dashboard/src/Index.vue
@@ -13,6 +13,7 @@ import EmailDomainView from './views/EmailDomainView.vue';
import EmailsEventlogView from './views/EmailsEventlogView.vue';
import EventlogView from './views/EventlogView.vue';
import MailboxesView from './views/MailboxesView.vue';
+import MailinglistsView from './views/MailinglistsView.vue';
import NetworkView from './views/NetworkView.vue';
import ProfileView from './views/ProfileView.vue';
import ServicesView from './views/ServicesView.vue';
@@ -34,6 +35,7 @@ const VIEWS = {
EMAIL_DOMAIN: 'email-domain',
EMAILS_EVENTLOG: 'emails-eventlog',
EMAILS_MAILBOXES: 'emails-mailboxes',
+ EMAILS_MAILINGLISTS: 'emails-mailinglists',
EVENTLOG: 'eventlog',
NETWORK: 'network',
PROFILE: 'profile',
@@ -69,6 +71,8 @@ function onHashChange() {
view.value = VIEWS.EMAILS_EVENTLOG;
} else if (v === VIEWS.EMAILS_MAILBOXES) {
view.value = VIEWS.EMAILS_MAILBOXES;
+ } else if (v === VIEWS.EMAILS_MAILINGLISTS) {
+ view.value = VIEWS.EMAILS_MAILINGLISTS;
} else if (v.indexOf(VIEWS.EMAIL) === 0) {
view.value = VIEWS.EMAIL_DOMAIN;
} else if (v === VIEWS.EVENTLOG) {
@@ -126,6 +130,7 @@ onMounted(async () => {
+
diff --git a/dashboard/src/components/CatchAllSettingsItem.vue b/dashboard/src/components/CatchAllSettingsItem.vue
index 491df5899..8351f5cc5 100644
--- a/dashboard/src/components/CatchAllSettingsItem.vue
+++ b/dashboard/src/components/CatchAllSettingsItem.vue
@@ -5,11 +5,13 @@ import { MultiSelect, InputGroup, Button, FormGroup } from 'pankow';
import SettingsItem from './SettingsItem.vue';
import DomainsModel from '../models/DomainsModel.js';
import MailModel from '../models/MailModel.js';
+import MailboxesModel from '../models/MailboxesModel.js';
const props = defineProps([ 'domainConfig' ]);
const domainsModel = DomainsModel.create();
const mailModel = MailModel.create();
+const mailboxesModel = MailboxesModel.create();
const busy = ref(true);
const addresses = ref([]);
@@ -46,7 +48,7 @@ onMounted(async () => {
// only for inbound enabled but then we have extra rest calls
for (const domain of result) {
- const [error, result] = await mailModel.listMailboxes(domain.domain);
+ const [error, result] = await mailboxesModel.list(domain.domain);
if (error) return console.error(error);
allAddresses.value = allAddresses.value.concat(result.map(mailbox => {
diff --git a/dashboard/src/components/MailinglistDialog.vue b/dashboard/src/components/MailinglistDialog.vue
new file mode 100644
index 000000000..2157245d6
--- /dev/null
+++ b/dashboard/src/components/MailinglistDialog.vue
@@ -0,0 +1,106 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/dashboard/src/models/MailModel.js b/dashboard/src/models/MailModel.js
index 3c9693bdd..244daa907 100644
--- a/dashboard/src/models/MailModel.js
+++ b/dashboard/src/models/MailModel.js
@@ -39,28 +39,6 @@ function create() {
if (result.status !== 200) return [result];
return [null, result.body.count];
},
- async listMailboxes(domain, search = '', page = 1, per_page = 1000) {
- let result;
- try {
- result = await fetcher.get(`${API_ORIGIN}/api/v1/mail/${domain}/mailboxes`, { search, page, per_page, access_token: accessToken });
- } catch (e) {
- return [e];
- }
-
- if (result.status !== 200) return [result];
- return [null, result.body.mailboxes];
- },
- async getMailbox(domain, mailboxName) {
- let result;
- try {
- result = await fetcher.get(`${API_ORIGIN}/api/v1/mail/${domain}/mailboxes/${mailboxName}`, { access_token: accessToken });
- } catch (e) {
- return [e];
- }
-
- if (result.status !== 200) return [result];
- return [null, result.body.mailbox];
- },
async setCatchallAddresses(domain, addresses) {
let result;
try {
diff --git a/dashboard/src/models/MailinglistsModel.js b/dashboard/src/models/MailinglistsModel.js
new file mode 100644
index 000000000..4ee14a381
--- /dev/null
+++ b/dashboard/src/models/MailinglistsModel.js
@@ -0,0 +1,82 @@
+
+import { fetcher } from 'pankow';
+import { API_ORIGIN } from '../constants.js';
+
+function create() {
+ const accessToken = localStorage.token;
+
+ return {
+ async list(domain) {
+ let result;
+ try {
+ result = await fetcher.get(`${API_ORIGIN}/api/v1/mail/${domain}/lists`, { page: 1, per_page: 1000, access_token: accessToken });
+ } catch (e) {
+ return [e];
+ }
+
+ if (result.status !== 200) return [result];
+ return [null, result.body.lists];
+ },
+ async get(domain, name) {
+ let result;
+ try {
+ result = await fetcher.get(`${API_ORIGIN}/api/v1/mail/${domain}/lists/${name}`, { access_token: accessToken });
+ } catch (e) {
+ return [e];
+ }
+
+ if (result.status !== 200) return [result];
+ return [null, result.body.list];
+ },
+ async add(domain, name, options) {
+ const data = {
+ name: name,
+ members: options.members || [],
+ membersOnly: !!options.membersOnly,
+ active: !!options.active,
+ };
+
+ let result;
+ try {
+ result = await fetcher.post(`${API_ORIGIN}/api/v1/mail/${domain}/lists`, data, { access_token: accessToken });
+ } catch (e) {
+ return [e];
+ }
+
+ if (result.status !== 201) return [result];
+ return [null];
+ },
+ async update(domain, name, options) {
+ const data = {
+ members: options.members || [],
+ membersOnly: !!options.membersOnly,
+ active: !!options.active,
+ };
+
+ let result;
+ try {
+ result = await fetcher.post(`${API_ORIGIN}/api/v1/mail/${domain}/lists/${name}`, data, { access_token: accessToken });
+ } catch (e) {
+ return [e];
+ }
+
+ if (result.status !== 204) return [result];
+ return [null];
+ },
+ async remove(domain, name) {
+ let result;
+ try {
+ result = await fetcher.del(`${API_ORIGIN}/api/v1/mail/${domain}/lists/${name}`, null, { access_token: accessToken });
+ } catch (e) {
+ return [e];
+ }
+
+ if (result.status !== 204) return [result];
+ return [null];
+ },
+ };
+}
+
+export default {
+ create,
+};
diff --git a/dashboard/src/views/MailboxesView.vue b/dashboard/src/views/MailboxesView.vue
index 0ccfe856b..19b5524ae 100644
--- a/dashboard/src/views/MailboxesView.vue
+++ b/dashboard/src/views/MailboxesView.vue
@@ -86,7 +86,7 @@ async function refresh() {
const usage = result;
- [error, result] = await mailModel.listMailboxes(domain);
+ [error, result] = await mailboxesModel.list(domain);
if (error) throw error;
result.forEach((m) => {
@@ -171,7 +171,7 @@ onMounted(async () => {
-
+
diff --git a/dashboard/src/views/MailinglistsView.vue b/dashboard/src/views/MailinglistsView.vue
new file mode 100644
index 000000000..907316153
--- /dev/null
+++ b/dashboard/src/views/MailinglistsView.vue
@@ -0,0 +1,143 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ mailinglist.members.join(', ') }}
+
+
+
+
+
+
+
+
+
+
+