diff --git a/server/apiv1/swagger.go b/server/apiv1/swagger.go index 7bb037e..b6b66fd 100644 --- a/server/apiv1/swagger.go +++ b/server/apiv1/swagger.go @@ -9,6 +9,13 @@ type infoResponse struct { Body appInformation } +// Web UI configuration +// swagger:response WebUIConfigurationResponse +type webUIConfigurationResponse struct { + // Web UI configuration settings + Body webUIConfiguration +} + // Message summary // swagger:response MessagesSummaryResponse type messagesSummaryResponse struct { diff --git a/server/apiv1/webui.go b/server/apiv1/webui.go new file mode 100644 index 0000000..60c0a39 --- /dev/null +++ b/server/apiv1/webui.go @@ -0,0 +1,54 @@ +package apiv1 + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/axllent/mailpit/config" +) + +// Response includes global web UI settings +// +// swagger:model WebUIConfiguration +type webUIConfiguration struct { + // Message Relay information + MessageRelay struct { + // Whether message relaying (release) is enabled + Enabled bool + // The configured SMTP server address + SMTPServer string + // Enforced Return-Path (if set) for relay bounces + ReturnPath string + } +} + +// WebUIConfig returns configuration settings for the web UI. +func WebUIConfig(w http.ResponseWriter, r *http.Request) { + // swagger:route GET /api/v1/webui application WebUIConfiguration + // + // # Get web UI configuration + // + // Returns configuration settings for the web UI. + // + // Produces: + // - application/json + // + // Schemes: http, https + // + // Responses: + // 200: WebUIConfigurationResponse + // default: ErrorResponse + conf := webUIConfiguration{} + + conf.MessageRelay.Enabled = config.ReleaseEnabled + if config.ReleaseEnabled { + conf.MessageRelay.SMTPServer = fmt.Sprintf("%s:%d", config.SMTPRelayConfig.Host, config.SMTPRelayConfig.Port) + conf.MessageRelay.ReturnPath = config.SMTPRelayConfig.ReturnPath + } + + bytes, _ := json.Marshal(conf) + + w.Header().Add("Content-Type", "application/json") + _, _ = w.Write(bytes) +} diff --git a/server/server.go b/server/server.go index 63c87d3..a5e23ce 100644 --- a/server/server.go +++ b/server/server.go @@ -88,6 +88,7 @@ func defaultRoutes() *mux.Router { r.HandleFunc(config.Webroot+"api/v1/message/{id}/headers", middleWareFunc(apiv1.GetHeaders)).Methods("GET") r.HandleFunc(config.Webroot+"api/v1/message/{id}", middleWareFunc(apiv1.GetMessage)).Methods("GET") r.HandleFunc(config.Webroot+"api/v1/info", middleWareFunc(apiv1.AppInfo)).Methods("GET") + r.HandleFunc(config.Webroot+"api/v1/webui", middleWareFunc(apiv1.WebUIConfig)).Methods("GET") return r } diff --git a/server/ui-src/App.vue b/server/ui-src/App.vue index 304f701..1130696 100644 --- a/server/ui-src/App.vue +++ b/server/ui-src/App.vue @@ -2,6 +2,7 @@ import commonMixins from './mixins.js'; import Message from './templates/Message.vue'; import MessageSummary from './templates/MessageSummary.vue'; +import MessageRelease from './templates/MessageRelease.vue'; import moment from 'moment'; import Tinycon from 'tinycon'; @@ -10,7 +11,8 @@ export default { components: { Message, - MessageSummary + MessageSummary, + MessageRelease }, data() { @@ -36,7 +38,9 @@ export default { selected: [], tcStatus: 0, appInfo: false, - lastLoaded: false + lastLoaded: false, + relayConfig: {}, + releaseAddresses: false, } }, @@ -87,6 +91,7 @@ export default { }); this.connect(); + this.getUISettings(); this.loadMessages(); }, @@ -147,6 +152,13 @@ export default { }); }, + getUISettings: function () { + let self = this; + self.get('api/v1/webui', null, function (response) { + self.relayConfig = response.data; + }); + }, + doSearch: function (e) { e.preventDefault(); this.loadMessages(); @@ -192,6 +204,7 @@ export default { openMessage: function (id) { let self = this; self.selected = []; + self.releaseAddresses = false; self.existingTags = JSON.parse(JSON.stringify(self.tags)); let uri = 'api/v1/message/' + self.currentPath @@ -550,6 +563,34 @@ export default { dl.target = '_blank'; dl.download = this.message.ID + '.' + ext; dl.click(); + }, + + initReleaseModal: function () { + this.releaseAddresses = false; + let addresses = []; + for (let i in this.message.To) { + addresses.push(this.message.To[i].Address) + } + for (let i in this.message.Cc) { + addresses.push(this.message.Cc[i].Address) + } + for (let i in this.message.Bcc) { + addresses.push(this.message.Bcc[i].Address) + } + + // include only unique email addresses, regardless of casing + let uAddresses = new Map(addresses.map(a => [a.toLowerCase(), a])); + this.releaseAddresses = [...uAddresses.values()]; + + let self = this; + window.setTimeout(function () { + // delay to allow elements to load + self.modal('ReleaseModal').show(); + + window.setTimeout(function () { + document.querySelector('#ReleaseModal input[role="combobox"]').focus() + }, 500); + }, 300); } } } @@ -572,6 +613,10 @@ export default { + @@ -963,4 +1008,10 @@ export default { + + +
diff --git a/server/ui-src/assets/styles.scss b/server/ui-src/assets/styles.scss index 01d8111..f278445 100644 --- a/server/ui-src/assets/styles.scss +++ b/server/ui-src/assets/styles.scss @@ -251,6 +251,10 @@ body.blur { input { font-size: 0.875em; } + + div { + cursor: text; // html5-tags + } } #DownloadBtn { @@ -264,6 +268,14 @@ body.blur { } } +#ReleaseModal { + .form-control.dropdown { + div { + @extend .form-control; + } + } +} + /* PrismJS 1.29.0 - modified! https://prismjs.com/download.html#themes=prism-coy&languages=markup+css */ code[class*="language-"], diff --git a/server/ui-src/templates/MessageRelease.vue b/server/ui-src/templates/MessageRelease.vue new file mode 100644 index 0000000..2ac4532 --- /dev/null +++ b/server/ui-src/templates/MessageRelease.vue @@ -0,0 +1,109 @@ + + + + +