diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 1a81e21..85b22ba 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -42,4 +42,4 @@ jobs: asset_name: mailpit-${{ matrix.goos }}-${{ matrix.goarch }} extra_files: LICENSE README.md md5sum: false - ldflags: -w -X "github.com/axllent/mailpit/cmd.Version=${{ steps.tag.outputs.tag }}" + ldflags: -w -X "github.com/axllent/mailpit/config.Version=${{ steps.tag.outputs.tag }}" diff --git a/Dockerfile b/Dockerfile index e6b0126..15f3a64 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ WORKDIR /app RUN apk add --no-cache git npm && \ npm install && npm run package && \ -CGO_ENABLED=0 go build -ldflags "-s -w -X github.com/axllent/mailpit/cmd.Version=${VERSION}" -o /mailpit +CGO_ENABLED=0 go build -ldflags "-s -w -X github.com/axllent/mailpit/config.Version=${VERSION}" -o /mailpit FROM alpine:latest diff --git a/cmd/version.go b/cmd/version.go index bcf2abd..c4e7b35 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -5,21 +5,11 @@ import ( "os" "runtime" + "github.com/axllent/mailpit/config" "github.com/axllent/mailpit/updater" "github.com/spf13/cobra" ) -var ( - // Version is the default application version, updated on release - Version = "dev" - - // Repo on Github for updater - Repo = "axllent/mailpit" - - // RepoBinaryName on Github for updater - RepoBinaryName = "mailpit" -) - // versionCmd represents the version command var versionCmd = &cobra.Command{ Use: "version", @@ -36,10 +26,10 @@ var versionCmd = &cobra.Command{ } fmt.Printf("%s %s compiled with %s on %s/%s\n", - os.Args[0], Version, runtime.Version(), runtime.GOOS, runtime.GOARCH) + os.Args[0], config.Version, runtime.Version(), runtime.GOOS, runtime.GOARCH) - latest, _, _, err := updater.GithubLatest(Repo, RepoBinaryName) - if err == nil && updater.GreaterThan(latest, Version) { + latest, _, _, err := updater.GithubLatest(config.Repo, config.RepoBinaryName) + if err == nil && updater.GreaterThan(latest, config.Version) { fmt.Printf( "\nUpdate available: %s\nRun `%s version -u` to update (requires read/write access to install directory).\n", latest, @@ -59,7 +49,7 @@ func init() { } func updateApp() error { - rel, err := updater.GithubUpdate(Repo, RepoBinaryName, Version) + rel, err := updater.GithubUpdate(config.Repo, config.RepoBinaryName, config.Version) if err != nil { return err } diff --git a/config/config.go b/config/config.go index 5fc906f..c4a6c88 100644 --- a/config/config.go +++ b/config/config.go @@ -58,6 +58,15 @@ var ( // ContentSecurityPolicy for HTTP server ContentSecurityPolicy = "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; frame-src 'self'; img-src * data: blob:; font-src 'self' data:; media-src 'self'; connect-src 'self' ws: wss:; object-src 'none'; base-uri 'self';" + + // Version is the default application version, updated on release + Version = "dev" + + // Repo on Github for updater + Repo = "axllent/mailpit" + + // RepoBinaryName on Github for updater + RepoBinaryName = "mailpit" ) // VerifyConfig wil do some basic checking diff --git a/server/apiv1/api.go b/server/apiv1/api.go index 044b7b1..049aba0 100644 --- a/server/apiv1/api.go +++ b/server/apiv1/api.go @@ -22,15 +22,6 @@ type MessagesResult struct { Messages []data.Summary `json:"messages"` } -// // Mailbox returns an message overview (stats) -// func Mailbox(w http.ResponseWriter, _ *http.Request) { -// res := storage.StatsGet() - -// bytes, _ := json.Marshal(res) -// w.Header().Add("Content-Type", "application/json") -// _, _ = w.Write(bytes) -// } - // Messages returns a paginated list of messages func Messages(w http.ResponseWriter, r *http.Request) { start, limit := getStartLimit(r) @@ -171,34 +162,6 @@ func DeleteMessages(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte("ok")) } -// // DeleteMessage (method: DELETE) deletes a single message -// func DeleteMessage(w http.ResponseWriter, r *http.Request) { -// vars := mux.Vars(r) - -// id := vars["id"] - -// err := storage.DeleteOneMessage(id) -// if err != nil { -// httpError(w, err.Error()) -// return -// } - -// w.Header().Add("Content-Type", "text/plain") -// _, _ = w.Write([]byte("ok")) -// } - -// SetAllRead (GET) will update all messages as read -// func SetAllRead(w http.ResponseWriter, r *http.Request) { -// err := storage.MarkAllRead() -// if err != nil { -// httpError(w, err.Error()) -// return -// } - -// w.Header().Add("Content-Type", "text/plain") -// _, _ = w.Write([]byte("ok")) -// } - // SetReadStatus (method: PUT) will update the status to Read/Unread for all provided IDs func SetReadStatus(w http.ResponseWriter, r *http.Request) { decoder := json.NewDecoder(r.Body) diff --git a/server/apiv1/info.go b/server/apiv1/info.go new file mode 100644 index 0000000..430424e --- /dev/null +++ b/server/apiv1/info.go @@ -0,0 +1,52 @@ +package apiv1 + +import ( + "encoding/json" + "net/http" + "os" + "runtime" + + "github.com/axllent/mailpit/config" + "github.com/axllent/mailpit/storage" + "github.com/axllent/mailpit/updater" +) + +type appVersion struct { + Version string + LatestVersion string + Database string + DatabaseSize int64 + Messages int + Memory uint64 +} + +// AppInfo returns some basic details about the running app, and latest release. +func AppInfo(w http.ResponseWriter, r *http.Request) { + + info := appVersion{} + info.Version = config.Version + + latest, _, _, err := updater.GithubLatest(config.Repo, config.RepoBinaryName) + if err == nil { + info.LatestVersion = latest + } + + info.Database = config.DataFile + + db, err := os.Stat(info.Database) + if err == nil { + info.DatabaseSize = db.Size() + } + + info.Messages = storage.CountTotal() + + var m runtime.MemStats + runtime.ReadMemStats(&m) + + info.Memory = m.Sys - m.HeapReleased + + bytes, _ := json.Marshal(info) + + w.Header().Add("Content-Type", "application/json") + _, _ = w.Write(bytes) +} diff --git a/server/server.go b/server/server.go index 192160d..c298967 100644 --- a/server/server.go +++ b/server/server.go @@ -66,6 +66,7 @@ func defaultRoutes() *mux.Router { r.HandleFunc("/api/v1/message/{id}/part/{partID}", middleWareFunc(apiv1.DownloadAttachment)).Methods("GET") r.HandleFunc("/api/v1/message/{id}/part/{partID}/thumb", middleWareFunc(apiv1.Thumbnail)).Methods("GET") r.HandleFunc("/api/v1/message/{id}", middleWareFunc(apiv1.Message)).Methods("GET") + r.HandleFunc("/api/v1/info", middleWareFunc(apiv1.AppInfo)).Methods("GET") return r } diff --git a/server/ui-src/App.vue b/server/ui-src/App.vue index 1a92d2a..0f36bfe 100644 --- a/server/ui-src/App.vue +++ b/server/ui-src/App.vue @@ -28,7 +28,8 @@ export default { notificationsSupported: false, notificationsEnabled: false, selected: [], - tcStatus: 0 + tcStatus: 0, + appInfo : false, } }, watch: { @@ -421,7 +422,7 @@ export default { else if (Notification.permission !== "denied") { let self = this; Notification.requestPermission().then(function (permission) { - // If the user accepts, let's create a notification + // if the user accepts, let's create a notification if (permission === "granted") { self.browserNotify("Notifications enabled", "You will receive notifications when new mails are received."); self.notificationsEnabled = true; @@ -479,6 +480,14 @@ export default { isSelected: function(id) { return this.selected.indexOf(id) != -1; + }, + + loadInfo: function() { + let self = this; + self.get('api/v1/info', false, function(response) { + self.appInfo = response.data; + self.modal('AppInfoModal').show(); + }); } } } @@ -625,13 +634,9 @@ export default {
  • - - - GitHub - - / - - Docs + + + About
  • @@ -756,4 +761,59 @@ export default { + + diff --git a/server/ui-src/mixins.js b/server/ui-src/mixins.js index 7e7976b..5ef88c5 100644 --- a/server/ui-src/mixins.js +++ b/server/ui-src/mixins.js @@ -1,4 +1,6 @@ -import axios from 'axios' +import axios from 'axios'; +import { Modal } from 'bootstrap'; + // FakeModal is used to return a fake Bootstrap modal // if the ID returns nothing @@ -31,7 +33,7 @@ const commonMixins = { // The request was made and the server responded with a status code // that falls out of the range of 2xx if (error.response.data.Error) { - alert(error.response.data.Error) + alert(error.response.data.Error); } else { alert(error.response.data); } @@ -50,7 +52,7 @@ const commonMixins = { modal: function (id) { let e = document.getElementById(id); if (e) { - return bootstrap.Modal.getOrCreateInstance(e); + return Modal.getOrCreateInstance(e); } // in case there are open/close actions return new FakeModal(); @@ -209,4 +211,4 @@ const commonMixins = { } -export default commonMixins +export default commonMixins diff --git a/storage/database.go b/storage/database.go index 3e795f3..b9b2fb9 100644 --- a/storage/database.go +++ b/storage/database.go @@ -95,6 +95,8 @@ func InitDB() error { p = filepath.Clean(p) } + config.DataFile = p + logger.Log().Debugf("[db] opening database %s", p) var err error