mirror of
https://github.com/axllent/mailpit.git
synced 2026-03-03 18:47:01 +00:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9053651cc1 | ||
|
|
a9593030ab | ||
|
|
75a7c1cfd4 | ||
|
|
699a534632 | ||
|
|
53f8d34961 | ||
|
|
81d09aabd1 | ||
|
|
11eec7db30 | ||
|
|
6e6482f6ad | ||
|
|
1efbbb353b | ||
|
|
b61fbe371a | ||
|
|
a2b6107dd6 | ||
|
|
f457412f98 | ||
|
|
14f1d75dba | ||
|
|
ce838dc054 | ||
|
|
0d29f3db1a | ||
|
|
cbc77530e9 | ||
|
|
70e8edf648 | ||
|
|
4368541a96 | ||
|
|
4d511bd29d | ||
|
|
b0894a8064 | ||
|
|
5d32d5190d | ||
|
|
b7154963c5 | ||
|
|
001e9de123 | ||
|
|
b64a5b7991 | ||
|
|
906a697542 | ||
|
|
46dbde04ae | ||
|
|
a31a7c3d2c | ||
|
|
675704ca91 |
2
.github/workflows/build-docker.yml
vendored
2
.github/workflows/build-docker.yml
vendored
@@ -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
|
||||
|
||||
10
.github/workflows/release-build.yml
vendored
10
.github/workflows/release-build.yml
vendored
@@ -10,11 +10,15 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
goos: [linux, windows, darwin]
|
||||
goarch: ["386", amd64, arm64]
|
||||
goarch: ["386", amd64, arm, arm64]
|
||||
exclude:
|
||||
- goarch: "386"
|
||||
goos: darwin
|
||||
- goarch: arm64
|
||||
- goarch: "386"
|
||||
goos: windows
|
||||
- goarch: arm
|
||||
goos: darwin
|
||||
- goarch: arm
|
||||
goos: windows
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -42,4 +46,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 }}"
|
||||
|
||||
28
CHANGELOG.md
28
CHANGELOG.md
@@ -2,6 +2,34 @@
|
||||
|
||||
Notable changes to Mailpit will be documented in this file.
|
||||
|
||||
## 1.2.3
|
||||
|
||||
### API
|
||||
- Add limit and start parameters to search
|
||||
|
||||
### UI
|
||||
- Prevent double message index request on websocket connect
|
||||
|
||||
|
||||
## 1.2.2
|
||||
|
||||
### API
|
||||
- Add API endpoint to return message headers
|
||||
|
||||
### Libs
|
||||
- Update go modules
|
||||
|
||||
### Testing
|
||||
- Add API test for raw & message headers
|
||||
|
||||
|
||||
## 1.2.1
|
||||
|
||||
### UI
|
||||
- Update frontend modules
|
||||
- Add about app modal with version update notification
|
||||
|
||||
|
||||
## 1.2.0
|
||||
|
||||
### Feature
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -12,7 +12,7 @@ It acts as both an SMTP server, and provides a web interface to view all capture
|
||||
|
||||
Mailpit is inspired by [MailHog](#why-rewrite-mailhog), but much, much faster.
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
## Features
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
package data
|
||||
|
||||
import "time"
|
||||
|
||||
// MailboxSummary struct
|
||||
type MailboxSummary struct {
|
||||
Name string
|
||||
Slug string
|
||||
Total int
|
||||
Unread int
|
||||
LastMessage time.Time
|
||||
}
|
||||
|
||||
// WebsocketNotification struct for responses
|
||||
type WebsocketNotification struct {
|
||||
Type string
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
// MailboxStats struct for quick mailbox total/read lookups
|
||||
type MailboxStats struct {
|
||||
Total int
|
||||
Unread int
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
# Message
|
||||
|
||||
Returns a summary of the message and attachments.
|
||||
## Message summary
|
||||
|
||||
Returns a JSON summary of the message and attachments.
|
||||
|
||||
**URL** : `api/v1/message/<ID>`
|
||||
|
||||
@@ -70,6 +72,39 @@ Returns a summary of the message and attachments.
|
||||
|
||||
Returns the attachment using the MIME type provided by the attachment `ContentType`.
|
||||
|
||||
---
|
||||
## Headers
|
||||
|
||||
**URL** : `api/v1/message/<ID>/headers`
|
||||
|
||||
**Method** : `GET`
|
||||
|
||||
Returns all message headers as a JSON array.
|
||||
Each unique header key contains an array of one or more values (email headers can be listed multiple times.)
|
||||
|
||||
```json
|
||||
{
|
||||
"Content-Type": [
|
||||
"multipart/related; type=\"multipart/alternative\"; boundary=\"----=_NextPart_000_0013_01C6A60C.47EEAB80\""
|
||||
],
|
||||
"Date": [
|
||||
"Wed, 12 Jul 2006 23:38:30 +1200"
|
||||
],
|
||||
"Delivered-To": [
|
||||
"user@example.com",
|
||||
"user-alias@example.com"
|
||||
],
|
||||
"From": [
|
||||
"\"User Name\" \\u003remote@example.com\\u003e"
|
||||
],
|
||||
"Message-Id": [
|
||||
"\\u003c001701c6a5a7$b3205580$0201010a@HomeOfficeSM\\u003e"
|
||||
],
|
||||
....
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
## Raw (source) email
|
||||
|
||||
|
||||
@@ -10,9 +10,11 @@ Matching messages are returned in the order of latest received to oldest.
|
||||
|
||||
## Query parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|--------|----------|--------------|
|
||||
| query | string | true | Search query |
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|---------|----------|----------------------------|
|
||||
| query | string | true | Search query |
|
||||
| limit | integer | false | Limit results (default 50) |
|
||||
| start | integer | false | Pagination offset |
|
||||
|
||||
|
||||
## Response
|
||||
|
||||
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
27
go.mod
27
go.mod
@@ -8,9 +8,9 @@ require (
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/jhillyerd/enmime v0.10.0
|
||||
github.com/jhillyerd/enmime v0.10.1
|
||||
github.com/k3a/html2text v1.0.8
|
||||
github.com/klauspost/compress v1.15.9
|
||||
github.com/klauspost/compress v1.15.11
|
||||
github.com/leporo/sqlf v1.3.0
|
||||
github.com/mattn/go-shellwords v1.0.12
|
||||
github.com/mhale/smtpd v0.8.0
|
||||
@@ -20,7 +20,7 @@ require (
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/tg123/go-htpasswd v1.2.0
|
||||
golang.org/x/text v0.3.7
|
||||
modernc.org/sqlite v1.18.1
|
||||
modernc.org/sqlite v1.19.1
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -29,34 +29,33 @@ require (
|
||||
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
|
||||
github.com/cznic/ql v1.2.0 // indirect
|
||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f // indirect
|
||||
github.com/google/go-cmp v0.5.8 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/kr/pretty v0.3.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||
github.com/rivo/uniseg v0.3.4 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20220927061507-ef77025ab5aa // indirect
|
||||
github.com/rivo/uniseg v0.4.2 // indirect
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
||||
github.com/stretchr/testify v1.7.2 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d // indirect
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 // indirect
|
||||
golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b // indirect
|
||||
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b // indirect
|
||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 // indirect
|
||||
golang.org/x/net v0.0.0-20221004154528-8021a29435af // indirect
|
||||
golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875 // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
lukechampine.com/uint128 v1.2.0 // indirect
|
||||
modernc.org/cc/v3 v3.36.3 // indirect
|
||||
modernc.org/cc/v3 v3.40.0 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.9 // indirect
|
||||
modernc.org/libc v1.17.1 // indirect
|
||||
modernc.org/libc v1.20.2 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.2.1 // indirect
|
||||
modernc.org/memory v1.4.0 // indirect
|
||||
modernc.org/opt v0.1.3 // indirect
|
||||
modernc.org/strutil v1.1.3 // indirect
|
||||
modernc.org/token v1.0.1 // indirect
|
||||
|
||||
59
go.sum
59
go.sum
@@ -46,8 +46,7 @@ github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7w
|
||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
@@ -62,16 +61,16 @@ github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf
|
||||
github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
|
||||
github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba h1:QFQpJdgbON7I0jr2hYW7Bs+XV0qjc3d5tZoDnRFnqTg=
|
||||
github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
|
||||
github.com/jhillyerd/enmime v0.10.0 h1:DZEzhptPRBesvN3gf7K1BOh4rfpqdsdrEoxW1Edr/3s=
|
||||
github.com/jhillyerd/enmime v0.10.0/go.mod h1:Qpe8EEemJMFAF8+NZoWdpXvK2Yb9dRF0k/z6mkcDHsA=
|
||||
github.com/jhillyerd/enmime v0.10.1 h1:3VP8gFhK7R948YJBrna5bOgnTXEuPAoICo79kKkBKfA=
|
||||
github.com/jhillyerd/enmime v0.10.1/go.mod h1:Qpe8EEemJMFAF8+NZoWdpXvK2Yb9dRF0k/z6mkcDHsA=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/k3a/html2text v1.0.8 h1:rVanLhKilpnJUJs/CNKWzMC4YaQINGxK0rSG8ssmnV0=
|
||||
github.com/k3a/html2text v1.0.8/go.mod h1:ieEXykM67iT8lTvEWBh6fhpH4B23kB9OMKPdIBmgUqA=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY=
|
||||
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
||||
github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c=
|
||||
github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
@@ -86,12 +85,12 @@ github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peK
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
|
||||
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.10/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.14 h1:qZgc/Rwetq+MtyE18WhzjokPD93dNqLGNT3QJuLvBGw=
|
||||
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
|
||||
github.com/mhale/smtpd v0.8.0 h1:5JvdsehCg33PQrZBvFyDMMUDQmvbzVpZgKob7eYBJc0=
|
||||
github.com/mhale/smtpd v0.8.0/go.mod h1:MQl+y2hwIEQCXtNhe5+55n0GZOjSmeqORDIXbqUL3x4=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
@@ -100,12 +99,13 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20220927061507-ef77025ab5aa h1:tEkEyxYeZ43TR55QU/hsIt9aRGBxbgGuz9CGykjvogY=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20220927061507-ef77025ab5aa/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.3.4 h1:3Z3Eu6FGHZWSfNKJTOUiPatWwfc7DzJRU04jFUqJODw=
|
||||
github.com/rivo/uniseg v0.3.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8=
|
||||
github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
@@ -136,10 +136,11 @@ golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACk
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d h1:3qF+Z8Hkrw9sOhrFHti9TlB1Hkac1x+DNRkv0XQiFjo=
|
||||
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
|
||||
golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b h1:huxqepDufQpLLIRXiVkTvnxrzJlpwmIWAObmcCcUFr0=
|
||||
golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 h1:Lj6HJGCSn5AjxRAH2+r35Mir4icalbqku+CLUtjnvXY=
|
||||
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
@@ -148,8 +149,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210501142056-aec3718b3fa0/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b h1:ZmngSVLe/wycRns9MKikG9OWIEjGcGAkacif7oYQaUY=
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.0.0-20221004154528-8021a29435af h1:wv66FM3rLZGPdxpYL+ApnDe2HzHcTFta3z5nsc13wI4=
|
||||
golang.org/x/net v0.0.0-20221004154528-8021a29435af/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -161,8 +162,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 h1:UiNENfZ8gDvpiWw7IpOMQ27spWmThO1RwwdQVbJahJM=
|
||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875 h1:AzgQNqF+FKwyQ5LbVrVqOcuuFB67N47F9+htZYH0wFM=
|
||||
golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@@ -192,8 +193,8 @@ lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl
|
||||
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
|
||||
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
|
||||
modernc.org/cc/v3 v3.36.3 h1:uISP3F66UlixxWEcKuIWERa4TwrZENHSL8tWxZz8bHg=
|
||||
modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
|
||||
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
|
||||
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
|
||||
modernc.org/ccgo/v3 v3.16.9 h1:AXquSwg7GuMk11pIdw7fmO1Y/ybgazVkMhsZWCV0mHM=
|
||||
modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo=
|
||||
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
|
||||
@@ -201,25 +202,25 @@ modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/Er
|
||||
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
|
||||
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
||||
modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0=
|
||||
modernc.org/libc v1.17.1 h1:Q8/Cpi36V/QBfuQaFVeisEBs3WqoGAJprZzmf7TfEYI=
|
||||
modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s=
|
||||
modernc.org/libc v1.20.2 h1:9/C6hYLe+SNLricCd+WYkIGatWrQTZegOfmOcz5fPmY=
|
||||
modernc.org/libc v1.20.2/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0=
|
||||
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
|
||||
modernc.org/memory v1.2.1 h1:dkRh86wgmq/bJu2cAS2oqBCz/KsMZU7TUM4CibQ7eBs=
|
||||
modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||
modernc.org/memory v1.4.0 h1:crykUfNSnMAXaOJnnxcSzbUGMqkLWjklJKkBK2nwZwk=
|
||||
modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sqlite v1.18.1 h1:ko32eKt3jf7eqIkCgPAeHMBXw3riNSLhl2f3loEF7o8=
|
||||
modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4=
|
||||
modernc.org/sqlite v1.19.1 h1:8xmS5oLnZtAK//vnd4aTVj8VOeTAccEFOtUnIzfSw+4=
|
||||
modernc.org/sqlite v1.19.1/go.mod h1:UfQ83woKMaPW/ZBruK0T7YaFCrI+IE0LeWVY6pmnVms=
|
||||
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
|
||||
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
|
||||
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
|
||||
modernc.org/tcl v1.13.1 h1:npxzTwFTZYM8ghWicVIX1cRWzj7Nd8i6AqqX2p+IYao=
|
||||
modernc.org/tcl v1.14.0 h1:cO7oyRWEXweSJmjdbs1L86P52D9QmBy/CPFKmFvNYTU=
|
||||
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
|
||||
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
modernc.org/z v1.5.1 h1:RTNHdsrOpeoSeOF4FbzTo8gBYByaJ5xT7NgZ9ZqRiJM=
|
||||
modernc.org/z v1.6.0 h1:gLwAw6aS973K/k9EOJGlofauyMk4YOUiPDYzWnq/oXo=
|
||||
|
||||
362
package-lock.json
generated
362
package-lock.json
generated
@@ -25,9 +25,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.18.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.13.tgz",
|
||||
"integrity": "sha512-dgXcIfMuQ0kgzLB2b9tRZs7TTFFaGM2AbtA4fJgUUYukzGH4jwsS7hzQHEGs67jdehpm22vkgKwvbU+aEflgwg==",
|
||||
"version": "7.19.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.3.tgz",
|
||||
"integrity": "sha512-pJ9xOlNWHiy9+FuFP09DEAFbAn4JskgRsVcc169w2xRBC3FRGuQEwjeIMMND9L2zc0iEhO/tGv4Zq+km+hxNpQ==",
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
},
|
||||
@@ -61,36 +61,36 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-core": {
|
||||
"version": "3.2.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.38.tgz",
|
||||
"integrity": "sha512-/FsvnSu7Z+lkd/8KXMa4yYNUiqQrI22135gfsQYVGuh5tqEgOB0XqrUdb/KnCLa5+TmQLPwvyUnKMyCpu+SX3Q==",
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.40.tgz",
|
||||
"integrity": "sha512-2Dc3Stk0J/VyQ4OUr2yEC53kU28614lZS+bnrCbFSAIftBJ40g/2yQzf4mPBiFuqguMB7hyHaujdgZAQ67kZYA==",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.16.4",
|
||||
"@vue/shared": "3.2.38",
|
||||
"@vue/shared": "3.2.40",
|
||||
"estree-walker": "^2.0.2",
|
||||
"source-map": "^0.6.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-dom": {
|
||||
"version": "3.2.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.38.tgz",
|
||||
"integrity": "sha512-zqX4FgUbw56kzHlgYuEEJR8mefFiiyR3u96498+zWPsLeh1WKvgIReoNE+U7gG8bCUdvsrJ0JRmev0Ky6n2O0g==",
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.40.tgz",
|
||||
"integrity": "sha512-OZCNyYVC2LQJy4H7h0o28rtk+4v+HMQygRTpmibGoG9wZyomQiS5otU7qo3Wlq5UfHDw2RFwxb9BJgKjVpjrQw==",
|
||||
"dependencies": {
|
||||
"@vue/compiler-core": "3.2.38",
|
||||
"@vue/shared": "3.2.38"
|
||||
"@vue/compiler-core": "3.2.40",
|
||||
"@vue/shared": "3.2.40"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-sfc": {
|
||||
"version": "3.2.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.38.tgz",
|
||||
"integrity": "sha512-KZjrW32KloMYtTcHAFuw3CqsyWc5X6seb8KbkANSWt3Cz9p2qA8c1GJpSkksFP9ABb6an0FLCFl46ZFXx3kKpg==",
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.40.tgz",
|
||||
"integrity": "sha512-tzqwniIN1fu1PDHC3CpqY/dPCfN/RN1thpBC+g69kJcrl7mbGiHKNwbA6kJ3XKKy8R6JLKqcpVugqN4HkeBFFg==",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.16.4",
|
||||
"@vue/compiler-core": "3.2.38",
|
||||
"@vue/compiler-dom": "3.2.38",
|
||||
"@vue/compiler-ssr": "3.2.38",
|
||||
"@vue/reactivity-transform": "3.2.38",
|
||||
"@vue/shared": "3.2.38",
|
||||
"@vue/compiler-core": "3.2.40",
|
||||
"@vue/compiler-dom": "3.2.40",
|
||||
"@vue/compiler-ssr": "3.2.40",
|
||||
"@vue/reactivity-transform": "3.2.40",
|
||||
"@vue/shared": "3.2.40",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.25.7",
|
||||
"postcss": "^8.1.10",
|
||||
@@ -98,69 +98,69 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-ssr": {
|
||||
"version": "3.2.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.38.tgz",
|
||||
"integrity": "sha512-bm9jOeyv1H3UskNm4S6IfueKjUNFmi2kRweFIGnqaGkkRePjwEcfCVqyS3roe7HvF4ugsEkhf4+kIvDhip6XzQ==",
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.40.tgz",
|
||||
"integrity": "sha512-80cQcgasKjrPPuKcxwuCx7feq+wC6oFl5YaKSee9pV3DNq+6fmCVwEEC3vvkf/E2aI76rIJSOYHsWSEIxK74oQ==",
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.2.38",
|
||||
"@vue/shared": "3.2.38"
|
||||
"@vue/compiler-dom": "3.2.40",
|
||||
"@vue/shared": "3.2.40"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/reactivity": {
|
||||
"version": "3.2.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.38.tgz",
|
||||
"integrity": "sha512-6L4myYcH9HG2M25co7/BSo0skKFHpAN8PhkNPM4xRVkyGl1K5M3Jx4rp5bsYhvYze2K4+l+pioN4e6ZwFLUVtw==",
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.40.tgz",
|
||||
"integrity": "sha512-N9qgGLlZmtUBMHF9xDT4EkD9RdXde1Xbveb+niWMXuHVWQP5BzgRmE3SFyUBBcyayG4y1lhoz+lphGRRxxK4RA==",
|
||||
"dependencies": {
|
||||
"@vue/shared": "3.2.38"
|
||||
"@vue/shared": "3.2.40"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/reactivity-transform": {
|
||||
"version": "3.2.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.38.tgz",
|
||||
"integrity": "sha512-3SD3Jmi1yXrDwiNJqQ6fs1x61WsDLqVk4NyKVz78mkaIRh6d3IqtRnptgRfXn+Fzf+m6B1KxBYWq1APj6h4qeA==",
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.40.tgz",
|
||||
"integrity": "sha512-HQUCVwEaacq6fGEsg2NUuGKIhUveMCjOk8jGHqLXPI2w6zFoPrlQhwWEaINTv5kkZDXKEnCijAp+4gNEHG03yw==",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.16.4",
|
||||
"@vue/compiler-core": "3.2.38",
|
||||
"@vue/shared": "3.2.38",
|
||||
"@vue/compiler-core": "3.2.40",
|
||||
"@vue/shared": "3.2.40",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.25.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/runtime-core": {
|
||||
"version": "3.2.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.38.tgz",
|
||||
"integrity": "sha512-kk0qiSiXUU/IKxZw31824rxmFzrLr3TL6ZcbrxWTKivadoKupdlzbQM4SlGo4MU6Zzrqv4fzyUasTU1jDoEnzg==",
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.40.tgz",
|
||||
"integrity": "sha512-U1+rWf0H8xK8aBUZhnrN97yoZfHbjgw/bGUzfgKPJl69/mXDuSg8CbdBYBn6VVQdR947vWneQBFzdhasyzMUKg==",
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "3.2.38",
|
||||
"@vue/shared": "3.2.38"
|
||||
"@vue/reactivity": "3.2.40",
|
||||
"@vue/shared": "3.2.40"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/runtime-dom": {
|
||||
"version": "3.2.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.38.tgz",
|
||||
"integrity": "sha512-4PKAb/ck2TjxdMSzMsnHViOrrwpudk4/A56uZjhzvusoEU9xqa5dygksbzYepdZeB5NqtRw5fRhWIiQlRVK45A==",
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.40.tgz",
|
||||
"integrity": "sha512-AO2HMQ+0s2+MCec8hXAhxMgWhFhOPJ/CyRXnmTJ6XIOnJFLrH5Iq3TNwvVcODGR295jy77I6dWPj+wvFoSYaww==",
|
||||
"dependencies": {
|
||||
"@vue/runtime-core": "3.2.38",
|
||||
"@vue/shared": "3.2.38",
|
||||
"@vue/runtime-core": "3.2.40",
|
||||
"@vue/shared": "3.2.40",
|
||||
"csstype": "^2.6.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/server-renderer": {
|
||||
"version": "3.2.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.38.tgz",
|
||||
"integrity": "sha512-pg+JanpbOZ5kEfOZzO2bt02YHd+ELhYP8zPeLU1H0e7lg079NtuuSB8fjLdn58c4Ou8UQ6C1/P+528nXnLPAhA==",
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.40.tgz",
|
||||
"integrity": "sha512-gtUcpRwrXOJPJ4qyBpU3EyxQa4EkV8I4f8VrDePcGCPe4O/hd0BPS7v9OgjIQob6Ap8VDz9G+mGTKazE45/95w==",
|
||||
"dependencies": {
|
||||
"@vue/compiler-ssr": "3.2.38",
|
||||
"@vue/shared": "3.2.38"
|
||||
"@vue/compiler-ssr": "3.2.40",
|
||||
"@vue/shared": "3.2.40"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "3.2.38"
|
||||
"vue": "3.2.40"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/shared": {
|
||||
"version": "3.2.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.38.tgz",
|
||||
"integrity": "sha512-dTyhTIRmGXBjxJE+skC8tTWCGLCVc4wQgRRLt8+O9p5ewBAjoBwtCAkLPrtToSr1xltoe3st21Pv953aOZ7alg=="
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.40.tgz",
|
||||
"integrity": "sha512-0PLQ6RUtZM0vO3teRfzGi4ltLUO5aO+kLgwh4Um3THSR03rpQWLTuRCkuO5A41ITzwdWeKdPHtSARuPkoo5pCQ=="
|
||||
},
|
||||
"node_modules/anymatch": {
|
||||
"version": "3.1.2",
|
||||
@@ -199,9 +199,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/bootstrap": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.2.0.tgz",
|
||||
"integrity": "sha512-qlnS9GL6YZE6Wnef46GxGv1UpGGzAwO0aPL1yOjzDIJpeApeMvqV24iL+pjr2kU4dduoBA9fINKWKgMToobx9A==",
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.2.2.tgz",
|
||||
"integrity": "sha512-dEtzMTV71n6Fhmbg4fYJzQsw1N29hJKO1js5ackCgIpDcGid2ETMGC6zwSYw09v05Y+oRdQ9loC54zB1La3hHQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -213,7 +213,7 @@
|
||||
}
|
||||
],
|
||||
"peerDependencies": {
|
||||
"@popperjs/core": "^2.11.5"
|
||||
"@popperjs/core": "^2.11.6"
|
||||
}
|
||||
},
|
||||
"node_modules/bootstrap-icons": {
|
||||
@@ -281,9 +281,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "2.6.20",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.20.tgz",
|
||||
"integrity": "sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA=="
|
||||
"version": "2.6.21",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz",
|
||||
"integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w=="
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
@@ -599,9 +599,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-sass-plugin": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-sass-plugin/-/esbuild-sass-plugin-2.3.2.tgz",
|
||||
"integrity": "sha512-bNIV241S0vpy+F9U9oMbmlAD+GDzKLJp2+Z9rSRP8Rq8Nwmxh9roI0s3iB9d6Eii1A5WYgCK7HZeWPokw2rhSw==",
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-sass-plugin/-/esbuild-sass-plugin-2.3.3.tgz",
|
||||
"integrity": "sha512-EegGnUIsP5Y7FbwcGBD524F+cJaIAQU2LSOX9QtjgpqEmwnmfEh5f/aPJ1df5GxD3NgHQJspeRCV7spDHE3N6Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.14.13",
|
||||
@@ -609,23 +609,6 @@
|
||||
"sass": "^1.49.0"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-sass-plugin/node_modules/sass": {
|
||||
"version": "1.54.9",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.54.9.tgz",
|
||||
"integrity": "sha512-xb1hjASzEH+0L0WI9oFjqhRi51t/gagWnxLiwUNMltA0Ab6jIDkAacgKiGYKM9Jhy109osM7woEEai6SXeJo5Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chokidar": ">=3.0.0 <4.0.0",
|
||||
"immutable": "^4.0.0",
|
||||
"source-map-js": ">=0.6.2 <2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"sass": "sass.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-sunos-64": {
|
||||
"version": "0.14.54",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz",
|
||||
@@ -708,9 +691,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.1",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
|
||||
"integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==",
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
@@ -928,9 +911,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.16",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz",
|
||||
"integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==",
|
||||
"version": "8.4.17",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.17.tgz",
|
||||
"integrity": "sha512-UNxNOLQydcOFi41yHNMcKRZ39NeXlr8AxGuZJsdub8vIb12fHzcq37DTU/QtbI6WLxNg2gF9Z+8qtRwTj1UI1Q==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -993,6 +976,23 @@
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/sass": {
|
||||
"version": "1.55.0",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.55.0.tgz",
|
||||
"integrity": "sha512-Pk+PMy7OGLs9WaxZGJMn7S96dvlyVBwwtToX895WmCpAOr5YiJYEUJfiJidMuKb613z2xNWcXCHEuOvjZbqC6A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chokidar": ">=3.0.0 <4.0.0",
|
||||
"immutable": "^4.0.0",
|
||||
"source-map-js": ">=0.6.2 <2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"sass": "sass.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
@@ -1044,23 +1044,23 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vue": {
|
||||
"version": "3.2.38",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.2.38.tgz",
|
||||
"integrity": "sha512-hHrScEFSmDAWL0cwO4B6WO7D3sALZPbfuThDsGBebthrNlDxdJZpGR3WB87VbjpPh96mep1+KzukYEhpHDFa8Q==",
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.2.40.tgz",
|
||||
"integrity": "sha512-1mGHulzUbl2Nk3pfvI5aXYYyJUs1nm4kyvuz38u4xlQkLUn1i2R7nDbI4TufECmY8v1qNBHYy62bCaM+3cHP2A==",
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.2.38",
|
||||
"@vue/compiler-sfc": "3.2.38",
|
||||
"@vue/runtime-dom": "3.2.38",
|
||||
"@vue/server-renderer": "3.2.38",
|
||||
"@vue/shared": "3.2.38"
|
||||
"@vue/compiler-dom": "3.2.40",
|
||||
"@vue/compiler-sfc": "3.2.40",
|
||||
"@vue/runtime-dom": "3.2.40",
|
||||
"@vue/server-renderer": "3.2.40",
|
||||
"@vue/shared": "3.2.40"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/parser": {
|
||||
"version": "7.18.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.13.tgz",
|
||||
"integrity": "sha512-dgXcIfMuQ0kgzLB2b9tRZs7TTFFaGM2AbtA4fJgUUYukzGH4jwsS7hzQHEGs67jdehpm22vkgKwvbU+aEflgwg=="
|
||||
"version": "7.19.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.3.tgz",
|
||||
"integrity": "sha512-pJ9xOlNWHiy9+FuFP09DEAFbAn4JskgRsVcc169w2xRBC3FRGuQEwjeIMMND9L2zc0iEhO/tGv4Zq+km+hxNpQ=="
|
||||
},
|
||||
"@esbuild/linux-loong64": {
|
||||
"version": "0.14.54",
|
||||
@@ -1075,36 +1075,36 @@
|
||||
"integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw=="
|
||||
},
|
||||
"@vue/compiler-core": {
|
||||
"version": "3.2.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.38.tgz",
|
||||
"integrity": "sha512-/FsvnSu7Z+lkd/8KXMa4yYNUiqQrI22135gfsQYVGuh5tqEgOB0XqrUdb/KnCLa5+TmQLPwvyUnKMyCpu+SX3Q==",
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.40.tgz",
|
||||
"integrity": "sha512-2Dc3Stk0J/VyQ4OUr2yEC53kU28614lZS+bnrCbFSAIftBJ40g/2yQzf4mPBiFuqguMB7hyHaujdgZAQ67kZYA==",
|
||||
"requires": {
|
||||
"@babel/parser": "^7.16.4",
|
||||
"@vue/shared": "3.2.38",
|
||||
"@vue/shared": "3.2.40",
|
||||
"estree-walker": "^2.0.2",
|
||||
"source-map": "^0.6.1"
|
||||
}
|
||||
},
|
||||
"@vue/compiler-dom": {
|
||||
"version": "3.2.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.38.tgz",
|
||||
"integrity": "sha512-zqX4FgUbw56kzHlgYuEEJR8mefFiiyR3u96498+zWPsLeh1WKvgIReoNE+U7gG8bCUdvsrJ0JRmev0Ky6n2O0g==",
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.40.tgz",
|
||||
"integrity": "sha512-OZCNyYVC2LQJy4H7h0o28rtk+4v+HMQygRTpmibGoG9wZyomQiS5otU7qo3Wlq5UfHDw2RFwxb9BJgKjVpjrQw==",
|
||||
"requires": {
|
||||
"@vue/compiler-core": "3.2.38",
|
||||
"@vue/shared": "3.2.38"
|
||||
"@vue/compiler-core": "3.2.40",
|
||||
"@vue/shared": "3.2.40"
|
||||
}
|
||||
},
|
||||
"@vue/compiler-sfc": {
|
||||
"version": "3.2.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.38.tgz",
|
||||
"integrity": "sha512-KZjrW32KloMYtTcHAFuw3CqsyWc5X6seb8KbkANSWt3Cz9p2qA8c1GJpSkksFP9ABb6an0FLCFl46ZFXx3kKpg==",
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.40.tgz",
|
||||
"integrity": "sha512-tzqwniIN1fu1PDHC3CpqY/dPCfN/RN1thpBC+g69kJcrl7mbGiHKNwbA6kJ3XKKy8R6JLKqcpVugqN4HkeBFFg==",
|
||||
"requires": {
|
||||
"@babel/parser": "^7.16.4",
|
||||
"@vue/compiler-core": "3.2.38",
|
||||
"@vue/compiler-dom": "3.2.38",
|
||||
"@vue/compiler-ssr": "3.2.38",
|
||||
"@vue/reactivity-transform": "3.2.38",
|
||||
"@vue/shared": "3.2.38",
|
||||
"@vue/compiler-core": "3.2.40",
|
||||
"@vue/compiler-dom": "3.2.40",
|
||||
"@vue/compiler-ssr": "3.2.40",
|
||||
"@vue/reactivity-transform": "3.2.40",
|
||||
"@vue/shared": "3.2.40",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.25.7",
|
||||
"postcss": "^8.1.10",
|
||||
@@ -1112,66 +1112,66 @@
|
||||
}
|
||||
},
|
||||
"@vue/compiler-ssr": {
|
||||
"version": "3.2.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.38.tgz",
|
||||
"integrity": "sha512-bm9jOeyv1H3UskNm4S6IfueKjUNFmi2kRweFIGnqaGkkRePjwEcfCVqyS3roe7HvF4ugsEkhf4+kIvDhip6XzQ==",
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.40.tgz",
|
||||
"integrity": "sha512-80cQcgasKjrPPuKcxwuCx7feq+wC6oFl5YaKSee9pV3DNq+6fmCVwEEC3vvkf/E2aI76rIJSOYHsWSEIxK74oQ==",
|
||||
"requires": {
|
||||
"@vue/compiler-dom": "3.2.38",
|
||||
"@vue/shared": "3.2.38"
|
||||
"@vue/compiler-dom": "3.2.40",
|
||||
"@vue/shared": "3.2.40"
|
||||
}
|
||||
},
|
||||
"@vue/reactivity": {
|
||||
"version": "3.2.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.38.tgz",
|
||||
"integrity": "sha512-6L4myYcH9HG2M25co7/BSo0skKFHpAN8PhkNPM4xRVkyGl1K5M3Jx4rp5bsYhvYze2K4+l+pioN4e6ZwFLUVtw==",
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.40.tgz",
|
||||
"integrity": "sha512-N9qgGLlZmtUBMHF9xDT4EkD9RdXde1Xbveb+niWMXuHVWQP5BzgRmE3SFyUBBcyayG4y1lhoz+lphGRRxxK4RA==",
|
||||
"requires": {
|
||||
"@vue/shared": "3.2.38"
|
||||
"@vue/shared": "3.2.40"
|
||||
}
|
||||
},
|
||||
"@vue/reactivity-transform": {
|
||||
"version": "3.2.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.38.tgz",
|
||||
"integrity": "sha512-3SD3Jmi1yXrDwiNJqQ6fs1x61WsDLqVk4NyKVz78mkaIRh6d3IqtRnptgRfXn+Fzf+m6B1KxBYWq1APj6h4qeA==",
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.40.tgz",
|
||||
"integrity": "sha512-HQUCVwEaacq6fGEsg2NUuGKIhUveMCjOk8jGHqLXPI2w6zFoPrlQhwWEaINTv5kkZDXKEnCijAp+4gNEHG03yw==",
|
||||
"requires": {
|
||||
"@babel/parser": "^7.16.4",
|
||||
"@vue/compiler-core": "3.2.38",
|
||||
"@vue/shared": "3.2.38",
|
||||
"@vue/compiler-core": "3.2.40",
|
||||
"@vue/shared": "3.2.40",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.25.7"
|
||||
}
|
||||
},
|
||||
"@vue/runtime-core": {
|
||||
"version": "3.2.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.38.tgz",
|
||||
"integrity": "sha512-kk0qiSiXUU/IKxZw31824rxmFzrLr3TL6ZcbrxWTKivadoKupdlzbQM4SlGo4MU6Zzrqv4fzyUasTU1jDoEnzg==",
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.40.tgz",
|
||||
"integrity": "sha512-U1+rWf0H8xK8aBUZhnrN97yoZfHbjgw/bGUzfgKPJl69/mXDuSg8CbdBYBn6VVQdR947vWneQBFzdhasyzMUKg==",
|
||||
"requires": {
|
||||
"@vue/reactivity": "3.2.38",
|
||||
"@vue/shared": "3.2.38"
|
||||
"@vue/reactivity": "3.2.40",
|
||||
"@vue/shared": "3.2.40"
|
||||
}
|
||||
},
|
||||
"@vue/runtime-dom": {
|
||||
"version": "3.2.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.38.tgz",
|
||||
"integrity": "sha512-4PKAb/ck2TjxdMSzMsnHViOrrwpudk4/A56uZjhzvusoEU9xqa5dygksbzYepdZeB5NqtRw5fRhWIiQlRVK45A==",
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.40.tgz",
|
||||
"integrity": "sha512-AO2HMQ+0s2+MCec8hXAhxMgWhFhOPJ/CyRXnmTJ6XIOnJFLrH5Iq3TNwvVcODGR295jy77I6dWPj+wvFoSYaww==",
|
||||
"requires": {
|
||||
"@vue/runtime-core": "3.2.38",
|
||||
"@vue/shared": "3.2.38",
|
||||
"@vue/runtime-core": "3.2.40",
|
||||
"@vue/shared": "3.2.40",
|
||||
"csstype": "^2.6.8"
|
||||
}
|
||||
},
|
||||
"@vue/server-renderer": {
|
||||
"version": "3.2.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.38.tgz",
|
||||
"integrity": "sha512-pg+JanpbOZ5kEfOZzO2bt02YHd+ELhYP8zPeLU1H0e7lg079NtuuSB8fjLdn58c4Ou8UQ6C1/P+528nXnLPAhA==",
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.40.tgz",
|
||||
"integrity": "sha512-gtUcpRwrXOJPJ4qyBpU3EyxQa4EkV8I4f8VrDePcGCPe4O/hd0BPS7v9OgjIQob6Ap8VDz9G+mGTKazE45/95w==",
|
||||
"requires": {
|
||||
"@vue/compiler-ssr": "3.2.38",
|
||||
"@vue/shared": "3.2.38"
|
||||
"@vue/compiler-ssr": "3.2.40",
|
||||
"@vue/shared": "3.2.40"
|
||||
}
|
||||
},
|
||||
"@vue/shared": {
|
||||
"version": "3.2.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.38.tgz",
|
||||
"integrity": "sha512-dTyhTIRmGXBjxJE+skC8tTWCGLCVc4wQgRRLt8+O9p5ewBAjoBwtCAkLPrtToSr1xltoe3st21Pv953aOZ7alg=="
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.40.tgz",
|
||||
"integrity": "sha512-0PLQ6RUtZM0vO3teRfzGi4ltLUO5aO+kLgwh4Um3THSR03rpQWLTuRCkuO5A41ITzwdWeKdPHtSARuPkoo5pCQ=="
|
||||
},
|
||||
"anymatch": {
|
||||
"version": "3.1.2",
|
||||
@@ -1204,9 +1204,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"bootstrap": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.2.0.tgz",
|
||||
"integrity": "sha512-qlnS9GL6YZE6Wnef46GxGv1UpGGzAwO0aPL1yOjzDIJpeApeMvqV24iL+pjr2kU4dduoBA9fINKWKgMToobx9A==",
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.2.2.tgz",
|
||||
"integrity": "sha512-dEtzMTV71n6Fhmbg4fYJzQsw1N29hJKO1js5ackCgIpDcGid2ETMGC6zwSYw09v05Y+oRdQ9loC54zB1La3hHQ==",
|
||||
"requires": {}
|
||||
},
|
||||
"bootstrap-icons": {
|
||||
@@ -1257,9 +1257,9 @@
|
||||
}
|
||||
},
|
||||
"csstype": {
|
||||
"version": "2.6.20",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.20.tgz",
|
||||
"integrity": "sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA=="
|
||||
"version": "2.6.21",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz",
|
||||
"integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w=="
|
||||
},
|
||||
"delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
@@ -1418,27 +1418,14 @@
|
||||
}
|
||||
},
|
||||
"esbuild-sass-plugin": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-sass-plugin/-/esbuild-sass-plugin-2.3.2.tgz",
|
||||
"integrity": "sha512-bNIV241S0vpy+F9U9oMbmlAD+GDzKLJp2+Z9rSRP8Rq8Nwmxh9roI0s3iB9d6Eii1A5WYgCK7HZeWPokw2rhSw==",
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-sass-plugin/-/esbuild-sass-plugin-2.3.3.tgz",
|
||||
"integrity": "sha512-EegGnUIsP5Y7FbwcGBD524F+cJaIAQU2LSOX9QtjgpqEmwnmfEh5f/aPJ1df5GxD3NgHQJspeRCV7spDHE3N6Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esbuild": "^0.14.13",
|
||||
"resolve": "^1.22.1",
|
||||
"sass": "^1.49.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"sass": {
|
||||
"version": "1.54.9",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.54.9.tgz",
|
||||
"integrity": "sha512-xb1hjASzEH+0L0WI9oFjqhRi51t/gagWnxLiwUNMltA0Ab6jIDkAacgKiGYKM9Jhy109osM7woEEai6SXeJo5Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chokidar": ">=3.0.0 <4.0.0",
|
||||
"immutable": "^4.0.0",
|
||||
"source-map-js": ">=0.6.2 <2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"esbuild-sunos-64": {
|
||||
@@ -1484,9 +1471,9 @@
|
||||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.15.1",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
|
||||
"integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA=="
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
|
||||
},
|
||||
"form-data": {
|
||||
"version": "4.0.0",
|
||||
@@ -1635,9 +1622,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"postcss": {
|
||||
"version": "8.4.16",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz",
|
||||
"integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==",
|
||||
"version": "8.4.17",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.17.tgz",
|
||||
"integrity": "sha512-UNxNOLQydcOFi41yHNMcKRZ39NeXlr8AxGuZJsdub8vIb12fHzcq37DTU/QtbI6WLxNg2gF9Z+8qtRwTj1UI1Q==",
|
||||
"requires": {
|
||||
"nanoid": "^3.3.4",
|
||||
"picocolors": "^1.0.0",
|
||||
@@ -1675,6 +1662,17 @@
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"dev": true
|
||||
},
|
||||
"sass": {
|
||||
"version": "1.55.0",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.55.0.tgz",
|
||||
"integrity": "sha512-Pk+PMy7OGLs9WaxZGJMn7S96dvlyVBwwtToX895WmCpAOr5YiJYEUJfiJidMuKb613z2xNWcXCHEuOvjZbqC6A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chokidar": ">=3.0.0 <4.0.0",
|
||||
"immutable": "^4.0.0",
|
||||
"source-map-js": ">=0.6.2 <2.0.0"
|
||||
}
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
@@ -1711,15 +1709,15 @@
|
||||
}
|
||||
},
|
||||
"vue": {
|
||||
"version": "3.2.38",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.2.38.tgz",
|
||||
"integrity": "sha512-hHrScEFSmDAWL0cwO4B6WO7D3sALZPbfuThDsGBebthrNlDxdJZpGR3WB87VbjpPh96mep1+KzukYEhpHDFa8Q==",
|
||||
"version": "3.2.40",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.2.40.tgz",
|
||||
"integrity": "sha512-1mGHulzUbl2Nk3pfvI5aXYYyJUs1nm4kyvuz38u4xlQkLUn1i2R7nDbI4TufECmY8v1qNBHYy62bCaM+3cHP2A==",
|
||||
"requires": {
|
||||
"@vue/compiler-dom": "3.2.38",
|
||||
"@vue/compiler-sfc": "3.2.38",
|
||||
"@vue/runtime-dom": "3.2.38",
|
||||
"@vue/server-renderer": "3.2.38",
|
||||
"@vue/shared": "3.2.38"
|
||||
"@vue/compiler-dom": "3.2.40",
|
||||
"@vue/compiler-sfc": "3.2.40",
|
||||
"@vue/runtime-dom": "3.2.40",
|
||||
"@vue/server-renderer": "3.2.40",
|
||||
"@vue/shared": "3.2.40"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,12 +8,12 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/mail"
|
||||
"net/smtp"
|
||||
"os"
|
||||
"os/user"
|
||||
|
||||
"github.com/axllent/mailpit/logger"
|
||||
flag "github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
@@ -80,6 +80,6 @@ func Run() {
|
||||
err = smtp.SendMail(smtpAddr, nil, fromAddr, recip, body)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "error sending mail")
|
||||
log.Fatal(err)
|
||||
logger.Log().Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,34 +4,25 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/mail"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/axllent/mailpit/config"
|
||||
"github.com/axllent/mailpit/data"
|
||||
"github.com/axllent/mailpit/storage"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// MessagesResult struct
|
||||
type MessagesResult struct {
|
||||
Total int `json:"total"`
|
||||
Unread int `json:"unread"`
|
||||
Count int `json:"count"`
|
||||
Start int `json:"start"`
|
||||
Messages []data.Summary `json:"messages"`
|
||||
Total int `json:"total"`
|
||||
Unread int `json:"unread"`
|
||||
Count int `json:"count"`
|
||||
Start int `json:"start"`
|
||||
Messages []storage.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 as JSON
|
||||
func Messages(w http.ResponseWriter, r *http.Request) {
|
||||
start, limit := getStartLimit(r)
|
||||
|
||||
@@ -56,7 +47,7 @@ func Messages(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write(bytes)
|
||||
}
|
||||
|
||||
// Search returns a max of 200 of the latest messages
|
||||
// Search returns up to 200 of the latest messages as JSON
|
||||
func Search(w http.ResponseWriter, r *http.Request) {
|
||||
search := strings.TrimSpace(r.URL.Query().Get("query"))
|
||||
if search == "" {
|
||||
@@ -64,7 +55,9 @@ func Search(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
messages, err := storage.Search(search)
|
||||
start, limit := getStartLimit(r)
|
||||
|
||||
messages, err := storage.Search(search, start, limit)
|
||||
if err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
@@ -85,7 +78,7 @@ func Search(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write(bytes)
|
||||
}
|
||||
|
||||
// Message (method: GET) returns a *data.Message
|
||||
// Message (method: GET) returns the *data.Message as JSON
|
||||
func Message(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
|
||||
@@ -124,6 +117,32 @@ func DownloadAttachment(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write(a.Content)
|
||||
}
|
||||
|
||||
// Headers (method: GET) returns the message headers as JSON
|
||||
func Headers(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
|
||||
id := vars["id"]
|
||||
|
||||
data, err := storage.GetMessageRaw(id)
|
||||
if err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
reader := strings.NewReader(string(data))
|
||||
m, err := mail.ReadMessage(reader)
|
||||
if err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
headers := m.Header
|
||||
bytes, _ := json.Marshal(headers)
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
_, _ = w.Write(bytes)
|
||||
}
|
||||
|
||||
// DownloadRaw (method: GET) returns the full email source as plain text
|
||||
func DownloadRaw(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
@@ -171,34 +190,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)
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"embed"
|
||||
"io"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -47,10 +46,10 @@ func Listen() {
|
||||
|
||||
if config.UISSLCert != "" && config.UISSLKey != "" {
|
||||
logger.Log().Infof("[http] starting secure server on https://%s", config.HTTPListen)
|
||||
log.Fatal(http.ListenAndServeTLS(config.HTTPListen, config.UISSLCert, config.UISSLKey, nil))
|
||||
logger.Log().Fatal(http.ListenAndServeTLS(config.HTTPListen, config.UISSLCert, config.UISSLKey, nil))
|
||||
} else {
|
||||
logger.Log().Infof("[http] starting server on http://%s", config.HTTPListen)
|
||||
log.Fatal(http.ListenAndServe(config.HTTPListen, nil))
|
||||
logger.Log().Fatal(http.ListenAndServe(config.HTTPListen, nil))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,10 +61,12 @@ func defaultRoutes() *mux.Router {
|
||||
r.HandleFunc("/api/v1/messages", middleWareFunc(apiv1.SetReadStatus)).Methods("PUT")
|
||||
r.HandleFunc("/api/v1/messages", middleWareFunc(apiv1.DeleteMessages)).Methods("DELETE")
|
||||
r.HandleFunc("/api/v1/search", middleWareFunc(apiv1.Search)).Methods("GET")
|
||||
r.HandleFunc("/api/v1/message/{id}/raw", middleWareFunc(apiv1.DownloadRaw)).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}/raw", middleWareFunc(apiv1.DownloadRaw)).Methods("GET")
|
||||
r.HandleFunc("/api/v1/message/{id}/headers", middleWareFunc(apiv1.Headers)).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
|
||||
}
|
||||
|
||||
@@ -54,15 +54,24 @@ func Test_APIv1(t *testing.T) {
|
||||
}
|
||||
|
||||
// read first 10
|
||||
t.Log("Read first 10 messages")
|
||||
t.Log("Read first 10 messages including raw & headers")
|
||||
putIDS := []string{}
|
||||
for indx, msg := range m.Messages {
|
||||
if indx == 10 {
|
||||
break
|
||||
}
|
||||
|
||||
_, err := clientGet(ts.URL + "/api/v1/message/" + msg.ID)
|
||||
if err != nil {
|
||||
if _, err := clientGet(ts.URL + "/api/v1/message/" + msg.ID); err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
|
||||
// test RAW
|
||||
if _, err := clientGet(ts.URL + "/api/v1/message/" + msg.ID + "/raw"); err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
|
||||
// test headers
|
||||
if _, err := clientGet(ts.URL + "/api/v1/message/" + msg.ID + "/headers"); err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
|
||||
@@ -172,7 +181,9 @@ func assertSearchEqual(t *testing.T, uri, query string, count int) {
|
||||
t.Logf("Test search: %s", query)
|
||||
m := apiv1.MessagesResult{}
|
||||
|
||||
data, err := clientGet(uri + "?query=" + url.QueryEscape(query))
|
||||
limit := fmt.Sprintf("%d", count)
|
||||
|
||||
data, err := clientGet(uri + "?query=" + url.QueryEscape(query) + "&limit=" + limit)
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
|
||||
@@ -28,7 +28,9 @@ export default {
|
||||
notificationsSupported: false,
|
||||
notificationsEnabled: false,
|
||||
selected: [],
|
||||
tcStatus: 0
|
||||
tcStatus: 0,
|
||||
appInfo: false,
|
||||
lastLoaded: false,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -53,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);
|
||||
@@ -65,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";
|
||||
|
||||
@@ -75,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;
|
||||
@@ -120,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();
|
||||
},
|
||||
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();
|
||||
},
|
||||
viewPrev: function () {
|
||||
let s = this.start - this.limit;
|
||||
if (s < 0) {
|
||||
s = 0;
|
||||
}
|
||||
this.start = s;
|
||||
this.loadMessages();
|
||||
},
|
||||
|
||||
openMessage: function(id) {
|
||||
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) {
|
||||
@@ -175,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 + '"'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -194,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 + '"'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -227,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) {
|
||||
@@ -239,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();
|
||||
@@ -305,7 +316,7 @@ export default {
|
||||
},
|
||||
|
||||
// test of any selected emails are unread
|
||||
selectedHasUnread: function() {
|
||||
selectedHasUnread: function () {
|
||||
if (!this.selected.length) {
|
||||
return false;
|
||||
}
|
||||
@@ -316,9 +327,9 @@ export default {
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
|
||||
// test of any selected emails are read
|
||||
selectedHasRead: function() {
|
||||
selectedHasRead: function () {
|
||||
if (!this.selected.length) {
|
||||
return false;
|
||||
}
|
||||
@@ -331,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;
|
||||
@@ -345,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();
|
||||
@@ -354,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;
|
||||
}
|
||||
@@ -391,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;
|
||||
}
|
||||
@@ -411,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");
|
||||
@@ -421,7 +432,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;
|
||||
@@ -430,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
|
||||
}
|
||||
@@ -477,8 +488,17 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
isSelected: function(id) {
|
||||
isSelected: function (id) {
|
||||
return this.selected.indexOf(id) != -1;
|
||||
},
|
||||
|
||||
loadInfo: function (e) {
|
||||
e.preventDefault();
|
||||
let self = this;
|
||||
self.get('api/v1/info', false, function (response) {
|
||||
self.appInfo = response.data;
|
||||
self.modal('AppInfoModal').show();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -492,9 +512,10 @@ export default {
|
||||
<span class="ms-2">Mailpit</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col col-md-9 col-lg-10" v-if="message">
|
||||
<a class="btn btn-outline-secondary me-4 px-3" href="#" v-on:click="message=false" title="Return to messages">
|
||||
<a class="btn btn-outline-secondary me-4 px-3" href="#" v-on:click="message=false"
|
||||
title="Return to messages">
|
||||
<i class="bi bi-arrow-return-left"></i>
|
||||
</a>
|
||||
<button class="btn btn-outline-secondary me-2" title="Mark unread" v-on:click="markUnread">
|
||||
@@ -503,13 +524,16 @@ export default {
|
||||
<button class="btn btn-outline-secondary me-2" title="Delete message" v-on:click="deleteMessages">
|
||||
<i class="bi bi-trash-fill"></i> <span class="d-none d-md-inline">Delete</span>
|
||||
</button>
|
||||
<a class="btn btn-outline-secondary float-end" :class="messageNext ? '':'disabled'" :href="'#'+messageNext" title="View next message">
|
||||
<a class="btn btn-outline-secondary float-end" :class="messageNext ? '':'disabled'" :href="'#'+messageNext"
|
||||
title="View next message">
|
||||
<i class="bi bi-caret-right-fill"></i>
|
||||
</a>
|
||||
<a class="btn btn-outline-secondary ms-2 me-1 float-end" :class="messagePrev ? '': 'disabled'" :href="'#'+messagePrev" title="View previous message">
|
||||
<a class="btn btn-outline-secondary ms-2 me-1 float-end" :class="messagePrev ? '': 'disabled'"
|
||||
:href="'#'+messagePrev" title="View previous message">
|
||||
<i class="bi bi-caret-left-fill"></i>
|
||||
</a>
|
||||
<a :href="'api/v1/' + message.ID + '/raw?dl=1'" class="btn btn-outline-secondary me-2 float-end" title="Download message">
|
||||
<a :href="'api/v1/' + message.ID + '/raw?dl=1'" class="btn btn-outline-secondary me-2 float-end"
|
||||
title="Download message">
|
||||
<i class="bi bi-file-arrow-down-fill"></i> <span class="d-none d-md-inline">Download</span>
|
||||
</a>
|
||||
</div>
|
||||
@@ -522,24 +546,29 @@ export default {
|
||||
<span v-if="!total" class="ms-2">Mailpit</span>
|
||||
</a>
|
||||
<div v-if="total" class="d-flex bg-white border rounded-start flex-fill position-relative">
|
||||
<input type="text" class="form-control border-0" v-model.trim="search" placeholder="Search mailbox">
|
||||
<span class="btn btn-link position-absolute end-0 text-muted" v-if="search" v-on:click="resetSearch"><i class="bi bi-x-circle"></i></span>
|
||||
<input type="text" class="form-control border-0" v-model.trim="search"
|
||||
placeholder="Search mailbox">
|
||||
<span class="btn btn-link position-absolute end-0 text-muted" v-if="search"
|
||||
v-on:click="resetSearch"><i class="bi bi-x-circle"></i></span>
|
||||
</div>
|
||||
<button v-if="total" class="btn btn-outline-secondary" type="submit"><i class="bi bi-search"></i></button>
|
||||
<button v-if="total" class="btn btn-outline-secondary" type="submit"><i
|
||||
class="bi bi-search"></i></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-12 col-lg-5 text-end mt-2 mt-lg-0" v-if="!message && total">
|
||||
<button v-if="total" class="btn btn-outline-danger float-start d-md-none me-2" data-bs-toggle="modal" data-bs-target="#DeleteAllModal" title="Delete all messages">
|
||||
<button v-if="total" class="btn btn-outline-danger float-start d-md-none me-2" data-bs-toggle="modal"
|
||||
data-bs-target="#DeleteAllModal" title="Delete all messages">
|
||||
<i class="bi bi-trash-fill"></i>
|
||||
</button>
|
||||
|
||||
<button v-if="unread" class="btn btn-outline-primary float-start d-md-none" data-bs-toggle="modal" data-bs-target="#MarkAllReadModal" title="Mark all read">
|
||||
<button v-if="unread" class="btn btn-outline-primary float-start d-md-none" data-bs-toggle="modal"
|
||||
data-bs-target="#MarkAllReadModal" title="Mark all read">
|
||||
<i class="bi bi-check2-square"></i>
|
||||
</button>
|
||||
|
||||
<select v-model="limit" v-on:change="loadMessages"
|
||||
class="form-select form-select-sm d-inline w-auto me-2" v-if="!searching">
|
||||
<select v-model="limit" v-on:change="loadMessages" class="form-select form-select-sm d-inline w-auto me-2"
|
||||
v-if="!searching">
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
@@ -550,12 +579,15 @@ export default {
|
||||
</span>
|
||||
<span v-else>
|
||||
<small>
|
||||
<b>{{ formatNumber(start + 1) }}-{{ formatNumber(start + items.length) }}</b> of <b>{{ formatNumber(total) }}</b>
|
||||
<b>{{ formatNumber(start + 1) }}-{{ formatNumber(start + items.length) }}</b> of <b>{{
|
||||
formatNumber(total) }}</b>
|
||||
</small>
|
||||
<button class="btn btn-outline-secondary ms-2 me-1" :disabled="!canPrev" v-on:click="viewPrev" v-if="!searching" :title="'View previous '+limit+' messages'">
|
||||
<button class="btn btn-outline-secondary ms-2 me-1" :disabled="!canPrev" v-on:click="viewPrev"
|
||||
v-if="!searching" :title="'View previous '+limit+' messages'">
|
||||
<i class="bi bi-caret-left-fill"></i>
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary" :disabled="!canNext" v-on:click="viewNext" v-if="!searching" :title="'View next '+limit+' messages'">
|
||||
<button class="btn btn-outline-secondary" :disabled="!canNext" v-on:click="viewNext" v-if="!searching"
|
||||
:title="'View next '+limit+' messages'">
|
||||
<i class="bi bi-caret-right-fill"></i>
|
||||
</button>
|
||||
</span>
|
||||
@@ -576,7 +608,7 @@ export default {
|
||||
<a class="position-relative ps-0" href="#" v-on:click="reloadMessages">
|
||||
<i class="bi bi-envelope me-1" v-if="isConnected"></i>
|
||||
<i class="bi bi-arrow-clockwise me-1" v-else></i>
|
||||
Inbox
|
||||
Inbox
|
||||
<span class="badge rounded-pill text-bg-primary ms-1" title="Unread messages" v-if="unread">
|
||||
{{ formatNumber(unread) }}
|
||||
</span>
|
||||
@@ -597,7 +629,8 @@ export default {
|
||||
|
||||
<li class="my-3" v-if="selected.length > 0">
|
||||
<b class="me-2">Selected {{selected.length}}</b>
|
||||
<button class="btn btn-sm text-muted" v-on:click="selected=[]" title="Unselect messages"><i class="bi bi-x-circle"></i></button>
|
||||
<button class="btn btn-sm text-muted" v-on:click="selected=[]" title="Unselect messages"><i
|
||||
class="bi bi-x-circle"></i></button>
|
||||
</li>
|
||||
<li class="my-3 ms-2" v-if="selected.length > 0 && selectedHasUnread()">
|
||||
<a href="#" v-on:click="markSelectedRead">
|
||||
@@ -619,19 +652,16 @@ export default {
|
||||
</li>
|
||||
|
||||
<li class="my-3" v-if="notificationsSupported && !notificationsEnabled">
|
||||
<a href="#" data-bs-toggle="modal" data-bs-target="#EnableNotificationsModal" title="Enable browser notifications">
|
||||
<a href="#" data-bs-toggle="modal" data-bs-target="#EnableNotificationsModal"
|
||||
title="Enable browser notifications">
|
||||
<i class="bi bi-bell"></i>
|
||||
Enable alerts
|
||||
</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>
|
||||
@@ -640,8 +670,9 @@ export default {
|
||||
<div class="col-lg-10 col-md-9 mh-100 pe-0">
|
||||
<div class="mh-100" style="overflow-y: auto;" :class="message ? 'd-none':''" id="message-page">
|
||||
<div class="list-group" v-if="items.length">
|
||||
<a v-for="message in items" :href="'#'+message.ID"
|
||||
v-on:click.ctrl="toggleSelected($event, message.ID)" v-on:click.shift="selectRange($event, message.ID)"
|
||||
<a v-for="message in items" :href="'#'+message.ID"
|
||||
v-on:click.ctrl="toggleSelected($event, message.ID)"
|
||||
v-on:click.shift="selectRange($event, message.ID)"
|
||||
class="row message d-flex small list-group-item list-group-item-action border-start-0 border-end-0"
|
||||
:class="message.Read ? 'read':'', isSelected(message.ID) ? 'selected':''">
|
||||
<div class="col-lg-3">
|
||||
@@ -650,10 +681,12 @@ export default {
|
||||
{{ getRelativeCreated(message) }}
|
||||
</div>
|
||||
<div class="text-truncate d-lg-none privacy">
|
||||
<span v-if="message.From" :title="message.From.Address">{{ message.From.Name ? message.From.Name : message.From.Address }}</span>
|
||||
</div>
|
||||
<span v-if="message.From" :title="message.From.Address">{{ message.From.Name ?
|
||||
message.From.Name : message.From.Address }}</span>
|
||||
</div>
|
||||
<div class="text-truncate d-none d-lg-block privacy">
|
||||
<b v-if="message.From" :title="message.From.Address">{{ message.From.Name ? message.From.Name : message.From.Address }}</b>
|
||||
<b v-if="message.From" :title="message.From.Address">{{ message.From.Name ?
|
||||
message.From.Name : message.From.Address }}</b>
|
||||
</div>
|
||||
<div class="d-none d-lg-block text-truncate text-muted small privacy">
|
||||
{{ getPrimaryEmailTo(message) }}
|
||||
@@ -708,14 +741,16 @@ export default {
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-danger" data-bs-dismiss="modal" v-on:click="deleteAll">Delete</button>
|
||||
<button type="button" class="btn btn-danger" data-bs-dismiss="modal"
|
||||
v-on:click="deleteAll">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="MarkAllReadModal" tabindex="-1" aria-labelledby="MarkAllReadModalLabel" aria-hidden="true">
|
||||
<div class="modal fade" id="MarkAllReadModal" tabindex="-1" aria-labelledby="MarkAllReadModalLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
@@ -727,14 +762,16 @@ export default {
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" v-on:click="markAllRead">Confirm</button>
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal"
|
||||
v-on:click="markAllRead">Confirm</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="EnableNotificationsModal" tabindex="-1" aria-labelledby="EnableNotificationsModalLabel" aria-hidden="true">
|
||||
<div class="modal fade" id="EnableNotificationsModal" tabindex="-1" aria-labelledby="EnableNotificationsModalLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
@@ -744,16 +781,74 @@ export default {
|
||||
<div class="modal-body">
|
||||
<p class="h4">Get browser notifications when Mailpit receives a new mail?</p>
|
||||
<p>
|
||||
Note that your browser will ask you for confirmation when you click <code>enable notifications</code>,
|
||||
Note that your browser will ask you for confirmation when you click
|
||||
<code>enable notifications</code>,
|
||||
and that you must have Mailpit open in a browser tab to be able to receive the notifications.
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" v-on:click="requestNotifications">Enable notifications</button>
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal"
|
||||
v-on:click="requestNotifications">Enable notifications</button>
|
||||
</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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
package websockets
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/axllent/mailpit/config"
|
||||
"github.com/axllent/mailpit/logger"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
@@ -117,7 +117,7 @@ func ServeWs(hub *Hub, w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
logger.Log().Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ package websockets
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/axllent/mailpit/data"
|
||||
"github.com/axllent/mailpit/logger"
|
||||
)
|
||||
|
||||
@@ -27,6 +26,12 @@ type Hub struct {
|
||||
unregister chan *Client
|
||||
}
|
||||
|
||||
// WebsocketNotification struct for responses
|
||||
type WebsocketNotification struct {
|
||||
Type string
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
// NewHub returns a new hub configuration
|
||||
func NewHub() *Hub {
|
||||
return &Hub{
|
||||
@@ -68,7 +73,7 @@ func Broadcast(t string, msg interface{}) {
|
||||
return
|
||||
}
|
||||
|
||||
w := data.WebsocketNotification{}
|
||||
w := WebsocketNotification{}
|
||||
w.Type = t
|
||||
w.Data = msg
|
||||
b, err := json.Marshal(w)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Package storage handles all database actions
|
||||
package storage
|
||||
|
||||
import (
|
||||
@@ -19,7 +20,6 @@ import (
|
||||
|
||||
"github.com/GuiaBolso/darwin"
|
||||
"github.com/axllent/mailpit/config"
|
||||
"github.com/axllent/mailpit/data"
|
||||
"github.com/axllent/mailpit/logger"
|
||||
"github.com/axllent/mailpit/server/websockets"
|
||||
"github.com/jhillyerd/enmime"
|
||||
@@ -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
|
||||
@@ -229,7 +231,7 @@ func Store(body []byte) (string, error) {
|
||||
}
|
||||
|
||||
// return summary
|
||||
c := &data.Summary{}
|
||||
c := &Summary{}
|
||||
if err := json.Unmarshal(b, c); err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -245,8 +247,8 @@ func Store(body []byte) (string, error) {
|
||||
|
||||
// List returns a subset of messages from the mailbox,
|
||||
// sorted latest to oldest
|
||||
func List(start, limit int) ([]data.Summary, error) {
|
||||
results := []data.Summary{}
|
||||
func List(start, limit int) ([]Summary, error) {
|
||||
results := []Summary{}
|
||||
|
||||
q := sqlf.From("mailbox").
|
||||
Select(`ID, Data, Read`).
|
||||
@@ -258,7 +260,7 @@ func List(start, limit int) ([]data.Summary, error) {
|
||||
var id string
|
||||
var summary string
|
||||
var read int
|
||||
em := data.Summary{}
|
||||
em := Summary{}
|
||||
|
||||
if err := row.Scan(&id, &summary, &read); err != nil {
|
||||
logger.Log().Error(err)
|
||||
@@ -289,9 +291,9 @@ func List(start, limit int) ([]data.Summary, error) {
|
||||
// The search is broken up by segments (exact phrases can be quoted), and interprits specific terms such as:
|
||||
// is:read, is:unread, has:attachment, to:<term>, from:<term> & subject:<term>
|
||||
// Negative searches also also included by prefixing the search term with a `-` or `!`
|
||||
func Search(search string) ([]data.Summary, error) {
|
||||
results := []data.Summary{}
|
||||
start := time.Now()
|
||||
func Search(search string, start, limit int) ([]Summary, error) {
|
||||
results := []Summary{}
|
||||
tsStart := time.Now()
|
||||
|
||||
s := strings.ToLower(search)
|
||||
// add another quote if missing closing quote
|
||||
@@ -303,19 +305,18 @@ func Search(search string) ([]data.Summary, error) {
|
||||
p := shellwords.NewParser()
|
||||
args, err := p.Parse(s)
|
||||
if err != nil {
|
||||
// return errors.New("Your search contains invalid characters")
|
||||
panic(err)
|
||||
return results, errors.New("Your search contains invalid characters")
|
||||
}
|
||||
|
||||
// generate the SQL based on arguments
|
||||
q := searchParser(args)
|
||||
q := searchParser(args, start, limit)
|
||||
|
||||
if err := q.QueryAndClose(nil, db, func(row *sql.Rows) {
|
||||
var id string
|
||||
var summary string
|
||||
var read int
|
||||
var ignore string
|
||||
em := data.Summary{}
|
||||
em := Summary{}
|
||||
|
||||
if err := row.Scan(&id, &summary, &read, &ignore, &ignore, &ignore, &ignore); err != nil {
|
||||
logger.Log().Error(err)
|
||||
@@ -336,7 +337,7 @@ func Search(search string) ([]data.Summary, error) {
|
||||
return results, err
|
||||
}
|
||||
|
||||
elapsed := time.Since(start)
|
||||
elapsed := time.Since(tsStart)
|
||||
|
||||
logger.Log().Debugf("[db] search for \"%s\" in %s", search, elapsed)
|
||||
|
||||
@@ -346,7 +347,7 @@ func Search(search string) ([]data.Summary, error) {
|
||||
}
|
||||
|
||||
// GetMessage returns a data.Message generated from the mailbox_data collection.
|
||||
func GetMessage(id string) (*data.Message, error) {
|
||||
func GetMessage(id string) (*Message, error) {
|
||||
raw, err := GetMessageRaw(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -369,7 +370,7 @@ func GetMessage(id string) (*data.Message, error) {
|
||||
|
||||
date, _ := env.Date()
|
||||
|
||||
obj := data.Message{
|
||||
obj := Message{
|
||||
ID: id,
|
||||
Read: true,
|
||||
From: from,
|
||||
@@ -382,28 +383,26 @@ func GetMessage(id string) (*data.Message, error) {
|
||||
Text: env.Text,
|
||||
}
|
||||
|
||||
html := env.HTML
|
||||
|
||||
// strip base tags
|
||||
var re = regexp.MustCompile(`(?U)<base .*>`)
|
||||
html = re.ReplaceAllString(html, "")
|
||||
html := re.ReplaceAllString(env.HTML, "")
|
||||
obj.HTML = html
|
||||
|
||||
for _, i := range env.Inlines {
|
||||
if i.FileName != "" || i.ContentID != "" {
|
||||
obj.Inline = append(obj.Inline, data.AttachmentSummary(i))
|
||||
obj.Inline = append(obj.Inline, AttachmentSummary(i))
|
||||
}
|
||||
}
|
||||
|
||||
for _, i := range env.OtherParts {
|
||||
if i.FileName != "" || i.ContentID != "" {
|
||||
obj.Inline = append(obj.Inline, data.AttachmentSummary(i))
|
||||
obj.Inline = append(obj.Inline, AttachmentSummary(i))
|
||||
}
|
||||
}
|
||||
|
||||
for _, a := range env.Attachments {
|
||||
if a.FileName != "" || a.ContentID != "" {
|
||||
obj.Attachments = append(obj.Attachments, data.AttachmentSummary(a))
|
||||
obj.Attachments = append(obj.Attachments, AttachmentSummary(a))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -648,18 +647,15 @@ func DeleteAllMessages() error {
|
||||
}
|
||||
|
||||
// StatsGet returns the total/unread statistics for a mailbox
|
||||
func StatsGet() data.MailboxStats {
|
||||
func StatsGet() MailboxStats {
|
||||
var (
|
||||
start = time.Now()
|
||||
total = CountTotal()
|
||||
unread = CountUnread()
|
||||
)
|
||||
|
||||
logger.Log().Debugf("[db] statistics calculated in %s", time.Since(start))
|
||||
|
||||
dbLastAction = time.Now()
|
||||
|
||||
return data.MailboxStats{
|
||||
return MailboxStats{
|
||||
Total: total,
|
||||
Unread: unread,
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ func TestSearch(t *testing.T) {
|
||||
search = fmt.Sprintf("\"the email body %d jdsauk dwqmdqw\"", i)
|
||||
}
|
||||
|
||||
summaries, err := Search(search)
|
||||
summaries, err := Search(search, 0, 100)
|
||||
if err != nil {
|
||||
t.Log("error ", err)
|
||||
t.Fail()
|
||||
@@ -196,7 +196,7 @@ func TestSearch(t *testing.T) {
|
||||
}
|
||||
|
||||
// search something that will return 200 rsults
|
||||
summaries, err := Search("This is the email body")
|
||||
summaries, err := Search("This is the email body", 0, testRuns)
|
||||
if err != nil {
|
||||
t.Log("error ", err)
|
||||
t.Fail()
|
||||
|
||||
@@ -8,7 +8,11 @@ import (
|
||||
)
|
||||
|
||||
// SearchParser returns the SQL syntax for the database search based on the search arguments
|
||||
func searchParser(args []string) *sqlf.Stmt {
|
||||
func searchParser(args []string, start, limit int) *sqlf.Stmt {
|
||||
if limit == 0 {
|
||||
limit = 50
|
||||
}
|
||||
|
||||
q := sqlf.From("mailbox").
|
||||
Select(`ID, Data, read,
|
||||
json_extract(Data, '$.To') as ToJSON,
|
||||
@@ -17,7 +21,12 @@ func searchParser(args []string) *sqlf.Stmt {
|
||||
json_extract(Data, '$.Attachments') as Attachments
|
||||
`).
|
||||
OrderBy("Sort DESC").
|
||||
Limit(200)
|
||||
Limit(limit).
|
||||
Offset(start)
|
||||
|
||||
if limit > 0 {
|
||||
q = q.Limit(limit)
|
||||
}
|
||||
|
||||
for _, w := range args {
|
||||
if cleanString(w) == "" {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// Package data contains the message & mailbox structs
|
||||
package data
|
||||
package storage
|
||||
|
||||
import (
|
||||
"net/mail"
|
||||
@@ -48,6 +47,12 @@ type Summary struct {
|
||||
Attachments int
|
||||
}
|
||||
|
||||
// MailboxStats struct for quick mailbox total/read lookups
|
||||
type MailboxStats struct {
|
||||
Total int
|
||||
Unread int
|
||||
}
|
||||
|
||||
// AttachmentSummary returns a summary of the attachment without any binary data
|
||||
func AttachmentSummary(a *enmime.Part) Attachment {
|
||||
o := Attachment{}
|
||||
Reference in New Issue
Block a user