From 11eec7db30f9b4a6784f16b46df23e213f9db67c Mon Sep 17 00:00:00 2001 From: Ralph Slooten Date: Thu, 13 Oct 2022 13:40:13 +1300 Subject: [PATCH 1/6] Add linux-arm to release matrix --- .github/workflows/release-build.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 95c69ccc4..26a2e9ee2 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -10,12 +10,16 @@ jobs: strategy: matrix: goos: [linux, windows, darwin] - goarch: ["386", amd64, arm64] + goarch: ["386", amd64, arm, arm64] exclude: - goarch: "386" goos: darwin - goarch: "386" goos: windows + - goarch: arm + goos: darwin + - goarch: arm + goos: windows steps: - uses: actions/checkout@v3 From 81d09aabd1ed1d7fe19126c0dfa204f941715ad4 Mon Sep 17 00:00:00 2001 From: Ralph Slooten Date: Thu, 13 Oct 2022 13:40:30 +1300 Subject: [PATCH 2/6] Add linux/386 docker builds --- .github/workflows/build-docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 8c1858c5b..16bb26f73 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -30,7 +30,7 @@ jobs: uses: docker/build-push-action@v3 with: context: . - platforms: linux/amd64,linux/arm64,linux/arm + platforms: linux/386,linux/amd64,linux/arm,linux/arm64 build-args: | "VERSION=${{ steps.tag.outputs.tag }}" push: true From 53f8d34961d1f0cf2c64592f941535c4abcbe7ac Mon Sep 17 00:00:00 2001 From: Ralph Slooten Date: Fri, 14 Oct 2022 17:17:01 +1300 Subject: [PATCH 3/6] UI: Prevent double message index request on websocket connect --- server/ui-src/App.vue | 302 +++++++++++++++++++++++------------------- 1 file changed, 168 insertions(+), 134 deletions(-) diff --git a/server/ui-src/App.vue b/server/ui-src/App.vue index b0542a4f7..6d191efa0 100644 --- a/server/ui-src/App.vue +++ b/server/ui-src/App.vue @@ -29,7 +29,8 @@ export default { notificationsEnabled: false, selected: [], tcStatus: 0, - appInfo : false, + appInfo: false, + lastLoaded: false, } }, watch: { @@ -54,11 +55,11 @@ export default { }, computed: { canPrev: function () { - return this.start > 0; - }, - canNext: function () { - return this.total > (this.start + this.count); - } + return this.start > 0; + }, + canNext: function () { + return this.total > (this.start + this.count); + } }, mounted() { this.currentPath = window.location.hash.slice(1); @@ -66,7 +67,7 @@ export default { this.currentPath = window.location.hash.slice(1); }); - this.notificationsSupported = 'https:' == document.location.protocol + this.notificationsSupported = 'https:' == document.location.protocol && ("Notification" in window && Notification.permission !== "denied"); this.notificationsEnabled = this.notificationsSupported && Notification.permission == "granted"; @@ -76,31 +77,40 @@ export default { fallback: false }); - this.loadMessages(); this.connect(); + this.loadMessages(); }, methods: { loadMessages: function () { - let self = this; - let params = {}; + + let now = Date.now() + // prevent double loading when websocket connects + if (this.lastLoaded && now - this.lastLoaded < 250) { + return; + } + this.lastLoaded = now; + + let self = this; + let params = {}; this.selected = []; - let uri = 'api/v1/messages'; - if (self.search) { - self.searching = true; + let uri = 'api/v1/messages'; + if (self.search) { + self.searching = true; self.items = []; - uri = 'api/v1/search' - self.start = 0; // search is displayed on one page - params['query'] = self.search; - } else { + uri = 'api/v1/search' + self.start = 0; // search is displayed on one page + params['query'] = self.search; + params['limit'] = 200; + } else { self.searching = false; - params['limit'] = self.limit; - if (self.start > 0) { - params['start'] = self.start; - } - } + params['limit'] = self.limit; + if (self.start > 0) { + params['start'] = self.start; + } + } - self.get(uri, params, function(response){ + self.get(uri, params, function (response) { self.total = response.data.total; self.unread = response.data.unread; self.count = response.data.count; @@ -121,46 +131,46 @@ export default { self.scrollInPlace = false }); - }, + }, - doSearch: function(e) { + doSearch: function (e) { e.preventDefault(); this.loadMessages(); }, - resetSearch: function(e) { + resetSearch: function (e) { e.preventDefault(); this.search = ''; this.scrollInPlace = true; this.loadMessages(); }, - reloadMessages: function() { + reloadMessages: function () { this.search = ""; - this.start = 0; + this.start = 0; this.loadMessages(); }, viewNext: function () { - this.start = parseInt(this.start, 10) + parseInt(this.limit, 10); - this.loadMessages(); - }, - - viewPrev: function () { - let s = this.start - this.limit; - if (s < 0) { - s = 0; - } - this.start = s; - this.loadMessages(); - }, - - openMessage: function(id) { + this.start = parseInt(this.start, 10) + parseInt(this.limit, 10); + this.loadMessages(); + }, + + viewPrev: function () { + let s = this.start - this.limit; + if (s < 0) { + s = 0; + } + this.start = s; + this.loadMessages(); + }, + + openMessage: function (id) { let self = this; self.selected = []; - let uri = 'api/v1/message/' + self.currentPath - self.get(uri, false, function(response) { + let uri = 'api/v1/message/' + self.currentPath + self.get(uri, false, function (response) { for (let i in self.items) { if (self.items[i].ID == self.currentPath) { if (!self.items[i].Read) { @@ -176,15 +186,15 @@ export default { let a = d.Inline[i]; if (a.ContentID != '') { d.HTML = d.HTML.replace( - new RegExp('cid:'+a.ContentID, 'g'), - window.location.origin+'/api/v1/message/'+d.ID+'/part/'+a.PartID + new RegExp('cid:' + a.ContentID, 'g'), + window.location.origin + '/api/v1/message/' + d.ID + '/part/' + a.PartID ); } if (a.FileName.match(/^[a-zA-Z0-9\_\-\.]+$/)) { // some old email clients use the filename d.HTML = d.HTML.replace( - new RegExp('src=(\'|")'+a.FileName+'(\'|")', 'g'), - 'src="'+window.location.origin+'/api/v1/message/'+d.ID+'/part/'+a.PartID+'"' + new RegExp('src=(\'|")' + a.FileName + '(\'|")', 'g'), + 'src="' + window.location.origin + '/api/v1/message/' + d.ID + '/part/' + a.PartID + '"' ); } } @@ -195,15 +205,15 @@ export default { let a = d.Attachments[i]; if (a.ContentID != '') { d.HTML = d.HTML.replace( - new RegExp('cid:'+a.ContentID, 'g'), - window.location.origin+'/api/v1/message/'+d.ID+'/part/'+a.PartID + new RegExp('cid:' + a.ContentID, 'g'), + window.location.origin + '/api/v1/message/' + d.ID + '/part/' + a.PartID ); } if (a.FileName.match(/^[a-zA-Z0-9\_\-\.]+$/)) { // some old email clients use the filename d.HTML = d.HTML.replace( - new RegExp('src=(\'|")'+a.FileName+'(\'|")', 'g'), - 'src="'+window.location.origin+'/api/v1/message/'+d.ID+'/part/'+a.PartID+'"' + new RegExp('src=(\'|")' + a.FileName + '(\'|")', 'g'), + 'src="' + window.location.origin + '/api/v1/message/' + d.ID + '/part/' + a.PartID + '"' ); } } @@ -228,7 +238,7 @@ export default { }, // universal handler to delete current or selected messages - deleteMessages: function() { + deleteMessages: function () { let ids = []; let self = this; if (self.message) { @@ -240,65 +250,65 @@ export default { return false; } let uri = 'api/v1/messages'; - self.delete(uri, {'ids': ids}, function(response) { + self.delete(uri, { 'ids': ids }, function (response) { window.location.hash = ""; self.scrollInPlace = true; self.loadMessages(); }); }, - deleteAll: function() { + deleteAll: function () { let self = this; let uri = 'api/v1/messages'; - self.delete(uri, false, function(response) { + self.delete(uri, false, function (response) { window.location.hash = ""; self.reloadMessages(); }); }, - markUnread: function() { + markUnread: function () { let self = this; if (!self.message) { return false; } let uri = 'api/v1/messages'; - self.put(uri, {'read': false, 'ids': [self.message.ID]}, function(response) { + self.put(uri, { 'read': false, 'ids': [self.message.ID] }, function (response) { window.location.hash = ""; self.scrollInPlace = true; self.loadMessages(); }); }, - markAllRead: function() { + markAllRead: function () { let self = this; let uri = 'api/v1/messages' - self.put(uri, {'read': true}, function(response) { + self.put(uri, { 'read': true }, function (response) { window.location.hash = ""; self.scrollInPlace = true; self.loadMessages(); }); }, - markSelectedRead: function() { + markSelectedRead: function () { let self = this; if (!self.selected.length) { return false; } let uri = 'api/v1/messages'; - self.put(uri, {'read': true, 'ids': self.selected}, function(response) { + self.put(uri, { 'read': true, 'ids': self.selected }, function (response) { window.location.hash = ""; self.scrollInPlace = true; self.loadMessages(); }); }, - markSelectedUnread: function() { + markSelectedUnread: function () { let self = this; if (!self.selected.length) { return false; } let uri = 'api/v1/messages'; - self.put(uri, {'read': false, 'ids': self.selected}, function(response) { + self.put(uri, { 'read': false, 'ids': self.selected }, function (response) { window.location.hash = ""; self.scrollInPlace = true; self.loadMessages(); @@ -306,7 +316,7 @@ export default { }, // test of any selected emails are unread - selectedHasUnread: function() { + selectedHasUnread: function () { if (!this.selected.length) { return false; } @@ -317,9 +327,9 @@ export default { } return false; }, - + // test of any selected emails are read - selectedHasRead: function() { + selectedHasRead: function () { if (!this.selected.length) { return false; } @@ -332,13 +342,13 @@ export default { }, // websocket connect - connect: function () { - let wsproto = location.protocol == 'https:' ? 'wss' : 'ws'; - let ws = new WebSocket( + connect: function () { + let wsproto = location.protocol == 'https:' ? 'wss' : 'ws'; + let ws = new WebSocket( wsproto + "://" + document.location.host + document.location.pathname + "api/events" ); - let self = this; - ws.onmessage = function (e) { + let self = this; + ws.onmessage = function (e) { let response = JSON.parse(e.data); if (!response) { return; @@ -346,7 +356,7 @@ export default { // new messages if (response.Type == "new" && response.Data) { if (!self.searching) { - if (self.start < 1) { + if (self.start < 1) { self.items.unshift(response.Data); if (self.items.length > self.limit) { self.items.pop(); @@ -355,36 +365,36 @@ export default { self.start++; } } - self.total++; + self.total++; self.unread++; let from = response.Data.From != null ? response.Data.From.Address : '[unknown]'; self.browserNotify("New mail from: " + from, response.Data.Subject); - } else if (response.Type == "prune") { + } else if (response.Type == "prune") { // messages have been deleted, reload messages to adjust self.scrollInPlace = true; self.loadMessages(); } - } + } - ws.onopen = function () { - self.isConnected = true; + ws.onopen = function () { + self.isConnected = true; self.loadMessages(); - } + } + + ws.onclose = function (e) { + self.isConnected = false; - ws.onclose = function (e) { - self.isConnected = false; - setTimeout(function () { self.connect(); // reconnect }, 1000); - } + } - ws.onerror = function (err) { - ws.close(); - } - }, + ws.onerror = function (err) { + ws.close(); + } + }, - getPrimaryEmailTo: function(message) { + getPrimaryEmailTo: function (message) { for (let i in message.To) { return message.To[i].Address; } @@ -392,12 +402,12 @@ export default { return '[ Undisclosed recipients ]'; }, - getRelativeCreated: function(message) { - let d = new Date(message.Created) - return moment(d).fromNow().toString(); - }, + getRelativeCreated: function (message) { + let d = new Date(message.Created) + return moment(d).fromNow().toString(); + }, - browserNotify: function(title, message) { + browserNotify: function (title, message) { if (!("Notification" in window)) { return; } @@ -412,7 +422,7 @@ export default { } }, - requestNotifications: function() { + requestNotifications: function () { // check if the browser supports notifications if (!("Notification" in window)) { alert("This browser does not support desktop notification"); @@ -431,26 +441,26 @@ export default { } }, - toggleSelected: function(e, id) { + toggleSelected: function (e, id) { e.preventDefault(); - + if (this.isSelected(id)) { - this.selected = this.selected.filter(function(ele){ - return ele != id; + this.selected = this.selected.filter(function (ele) { + return ele != id; }); } else { this.selected.push(id); } }, - selectRange: function(e, id) { + selectRange: function (e, id) { e.preventDefault(); let selecting = false; let lastSelected = this.selected.length > 0 && this.selected[this.selected.length - 1]; if (lastSelected == id) { - this.selected = this.selected.filter(function(ele){ - return ele != id; + this.selected = this.selected.filter(function (ele) { + return ele != id; }); return } @@ -478,14 +488,14 @@ export default { } }, - isSelected: function(id) { + isSelected: function (id) { return this.selected.indexOf(id) != -1; }, - loadInfo: function(e) { + loadInfo: function (e) { e.preventDefault(); let self = this; - self.get('api/v1/info', false, function(response) { + self.get('api/v1/info', false, function (response) { self.appInfo = response.data; self.modal('AppInfoModal').show(); }); @@ -502,9 +512,10 @@ export default { Mailpit - +
- + - + - + - + Download
@@ -532,24 +546,29 @@ export default { Mailpit
- - + +
- + -