mirror of
https://github.com/axllent/mailpit.git
synced 2025-04-17 12:06:22 +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 }}
|
||||
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 }}"
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
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}/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
|
||||
}
|
||||
|
@ -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 {
|
||||
</a>
|
||||
</li>
|
||||
<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">
|
||||
<i class="bi bi-github"></i>
|
||||
GitHub
|
||||
</a>
|
||||
/
|
||||
<a href="https://github.com/axllent/mailpit/wiki" target="_blank" class="text-muted ms-1">
|
||||
Docs
|
||||
<a href="#" class="text-muted" v-on:click="loadInfo">
|
||||
<i class="bi bi-info-circle-fill"></i>
|
||||
About
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
@ -756,4 +761,59 @@ export default {
|
||||
</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>
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user