mirror of
https://github.com/axllent/mailpit.git
synced 2025-04-25 12:25:04 +02:00
UI: Add about app modal with version update notification
This commit is contained in:
parent
675704ca91
commit
a31a7c3d2c
2
.github/workflows/release-build.yml
vendored
2
.github/workflows/release-build.yml
vendored
@ -42,4 +42,4 @@ jobs:
|
|||||||
asset_name: mailpit-${{ matrix.goos }}-${{ matrix.goarch }}
|
asset_name: mailpit-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||||
extra_files: LICENSE README.md
|
extra_files: LICENSE README.md
|
||||||
md5sum: false
|
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 }}"
|
||||||
|
@ -8,7 +8,7 @@ WORKDIR /app
|
|||||||
|
|
||||||
RUN apk add --no-cache git npm && \
|
RUN apk add --no-cache git npm && \
|
||||||
npm install && npm run package && \
|
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
|
FROM alpine:latest
|
||||||
|
@ -5,21 +5,11 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/axllent/mailpit/config"
|
||||||
"github.com/axllent/mailpit/updater"
|
"github.com/axllent/mailpit/updater"
|
||||||
"github.com/spf13/cobra"
|
"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
|
// versionCmd represents the version command
|
||||||
var versionCmd = &cobra.Command{
|
var versionCmd = &cobra.Command{
|
||||||
Use: "version",
|
Use: "version",
|
||||||
@ -36,10 +26,10 @@ var versionCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s compiled with %s on %s/%s\n",
|
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)
|
latest, _, _, err := updater.GithubLatest(config.Repo, config.RepoBinaryName)
|
||||||
if err == nil && updater.GreaterThan(latest, Version) {
|
if err == nil && updater.GreaterThan(latest, config.Version) {
|
||||||
fmt.Printf(
|
fmt.Printf(
|
||||||
"\nUpdate available: %s\nRun `%s version -u` to update (requires read/write access to install directory).\n",
|
"\nUpdate available: %s\nRun `%s version -u` to update (requires read/write access to install directory).\n",
|
||||||
latest,
|
latest,
|
||||||
@ -59,7 +49,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateApp() error {
|
func updateApp() error {
|
||||||
rel, err := updater.GithubUpdate(Repo, RepoBinaryName, Version)
|
rel, err := updater.GithubUpdate(config.Repo, config.RepoBinaryName, config.Version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -58,6 +58,15 @@ var (
|
|||||||
|
|
||||||
// ContentSecurityPolicy for HTTP server
|
// 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';"
|
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
|
// VerifyConfig wil do some basic checking
|
||||||
|
@ -22,15 +22,6 @@ type MessagesResult struct {
|
|||||||
Messages []data.Summary `json:"messages"`
|
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
|
// Messages returns a paginated list of messages
|
||||||
func Messages(w http.ResponseWriter, r *http.Request) {
|
func Messages(w http.ResponseWriter, r *http.Request) {
|
||||||
start, limit := getStartLimit(r)
|
start, limit := getStartLimit(r)
|
||||||
@ -171,34 +162,6 @@ func DeleteMessages(w http.ResponseWriter, r *http.Request) {
|
|||||||
_, _ = w.Write([]byte("ok"))
|
_, _ = 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
|
// SetReadStatus (method: PUT) will update the status to Read/Unread for all provided IDs
|
||||||
func SetReadStatus(w http.ResponseWriter, r *http.Request) {
|
func SetReadStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
decoder := json.NewDecoder(r.Body)
|
decoder := json.NewDecoder(r.Body)
|
||||||
|
52
server/apiv1/info.go
Normal file
52
server/apiv1/info.go
Normal file
@ -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)
|
||||||
|
}
|
@ -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}", 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}/part/{partID}/thumb", middleWareFunc(apiv1.Thumbnail)).Methods("GET")
|
||||||
r.HandleFunc("/api/v1/message/{id}", middleWareFunc(apiv1.Message)).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
|
return r
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,8 @@ export default {
|
|||||||
notificationsSupported: false,
|
notificationsSupported: false,
|
||||||
notificationsEnabled: false,
|
notificationsEnabled: false,
|
||||||
selected: [],
|
selected: [],
|
||||||
tcStatus: 0
|
tcStatus: 0,
|
||||||
|
appInfo : false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@ -421,7 +422,7 @@ export default {
|
|||||||
else if (Notification.permission !== "denied") {
|
else if (Notification.permission !== "denied") {
|
||||||
let self = this;
|
let self = this;
|
||||||
Notification.requestPermission().then(function (permission) {
|
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") {
|
if (permission === "granted") {
|
||||||
self.browserNotify("Notifications enabled", "You will receive notifications when new mails are received.");
|
self.browserNotify("Notifications enabled", "You will receive notifications when new mails are received.");
|
||||||
self.notificationsEnabled = true;
|
self.notificationsEnabled = true;
|
||||||
@ -479,6 +480,14 @@ export default {
|
|||||||
|
|
||||||
isSelected: function(id) {
|
isSelected: function(id) {
|
||||||
return this.selected.indexOf(id) != -1;
|
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 {
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="mt-5 position-fixed bottom-0 bg-white py-2 text-muted">
|
<li class="mt-5 position-fixed bottom-0 bg-white py-2 text-muted">
|
||||||
<a href="https://github.com/axllent/mailpit" target="_blank" class="text-muted me-1">
|
<a href="#" class="text-muted" v-on:click="loadInfo">
|
||||||
<i class="bi bi-github"></i>
|
<i class="bi bi-info-circle-fill"></i>
|
||||||
GitHub
|
About
|
||||||
</a>
|
|
||||||
/
|
|
||||||
<a href="https://github.com/axllent/mailpit/wiki" target="_blank" class="text-muted ms-1">
|
|
||||||
Docs
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -756,4 +761,59 @@ export default {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal -->
|
||||||
|
<div class="modal fade" id="AppInfoModal" tabindex="-1" aria-labelledby="AppInfoModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header" v-if="appInfo">
|
||||||
|
<h5 class="modal-title" id="AppInfoModalLabel">
|
||||||
|
Mailpit
|
||||||
|
<code>({{ appInfo.Version }})</code>
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<a class="btn btn-warning d-block mb-3" v-if="appInfo.Version != appInfo.LatestVersion"
|
||||||
|
:href="'https://github.com/axllent/mailpit/releases/tag/'+appInfo.LatestVersion">
|
||||||
|
A new version of Mailpit ({{ appInfo.LatestVersion }}) is available.
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<a class="btn btn-primary w-100" href="https://github.com/axllent/mailpit" target="_blank">
|
||||||
|
<i class="bi bi-github"></i>
|
||||||
|
Github
|
||||||
|
<i class="bi bi-box-arrow-up-right"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<a class="btn btn-primary w-100" href="https://github.com/axllent/mailpit/wiki" target="_blank">
|
||||||
|
Documentation
|
||||||
|
<i class="bi bi-box-arrow-up-right"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<div class="card border-secondary text-center">
|
||||||
|
<div class="card-header">Database size</div>
|
||||||
|
<div class="card-body text-secondary">
|
||||||
|
<h5 class="card-title">{{ getFileSize(appInfo.DatabaseSize) }} </h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<div class="card border-secondary text-center">
|
||||||
|
<div class="card-header">RAM usage</div>
|
||||||
|
<div class="card-body text-secondary">
|
||||||
|
<h5 class="card-title">{{ getFileSize(appInfo.Memory) }} </h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -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
|
// FakeModal is used to return a fake Bootstrap modal
|
||||||
// if the ID returns nothing
|
// if the ID returns nothing
|
||||||
@ -31,7 +33,7 @@ const commonMixins = {
|
|||||||
// The request was made and the server responded with a status code
|
// The request was made and the server responded with a status code
|
||||||
// that falls out of the range of 2xx
|
// that falls out of the range of 2xx
|
||||||
if (error.response.data.Error) {
|
if (error.response.data.Error) {
|
||||||
alert(error.response.data.Error)
|
alert(error.response.data.Error);
|
||||||
} else {
|
} else {
|
||||||
alert(error.response.data);
|
alert(error.response.data);
|
||||||
}
|
}
|
||||||
@ -50,7 +52,7 @@ const commonMixins = {
|
|||||||
modal: function (id) {
|
modal: function (id) {
|
||||||
let e = document.getElementById(id);
|
let e = document.getElementById(id);
|
||||||
if (e) {
|
if (e) {
|
||||||
return bootstrap.Modal.getOrCreateInstance(e);
|
return Modal.getOrCreateInstance(e);
|
||||||
}
|
}
|
||||||
// in case there are open/close actions
|
// in case there are open/close actions
|
||||||
return new FakeModal();
|
return new FakeModal();
|
||||||
|
@ -95,6 +95,8 @@ func InitDB() error {
|
|||||||
p = filepath.Clean(p)
|
p = filepath.Clean(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config.DataFile = p
|
||||||
|
|
||||||
logger.Log().Debugf("[db] opening database %s", p)
|
logger.Log().Debugf("[db] opening database %s", p)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
Loading…
x
Reference in New Issue
Block a user