mirror of
https://github.com/knadh/listmonk.git
synced 2025-10-12 08:17:07 +08:00
Merge branch 'refactor-frontend-path'
This commit is contained in:
commit
a97d81a8dc
7 changed files with 39 additions and 37 deletions
8
Makefile
8
Makefile
|
@ -23,7 +23,7 @@ STATIC := config.toml.sample \
|
||||||
schema.sql queries.sql \
|
schema.sql queries.sql \
|
||||||
static/public:/public \
|
static/public:/public \
|
||||||
static/email-templates \
|
static/email-templates \
|
||||||
frontend/dist/frontend:/frontend \
|
frontend/dist:/admin \
|
||||||
i18n:/i18n
|
i18n:/i18n
|
||||||
|
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
|
@ -40,14 +40,14 @@ $(FRONTEND_YARN_MODULES): frontend/package.json frontend/yarn.lock
|
||||||
$(BIN): $(shell find . -type f -name "*.go")
|
$(BIN): $(shell find . -type f -name "*.go")
|
||||||
CGO_ENABLED=0 go build -o ${BIN} -ldflags="-s -w -X 'main.buildString=${BUILDSTR}' -X 'main.versionString=${VERSION}'" cmd/*.go
|
CGO_ENABLED=0 go build -o ${BIN} -ldflags="-s -w -X 'main.buildString=${BUILDSTR}' -X 'main.versionString=${VERSION}'" cmd/*.go
|
||||||
|
|
||||||
# Run the backend in dev mode. The frontend assets in dev mode are loaded from disk from frontend/dist/frontend.
|
# Run the backend in dev mode. The frontend assets in dev mode are loaded from disk from frontend/dist.
|
||||||
.PHONY: run
|
.PHONY: run
|
||||||
run:
|
run:
|
||||||
CGO_ENABLED=0 go run -ldflags="-s -w -X 'main.buildString=${BUILDSTR}' -X 'main.versionString=${VERSION}' -X 'main.frontendDir=frontend/dist/frontend'" cmd/*.go
|
CGO_ENABLED=0 go run -ldflags="-s -w -X 'main.buildString=${BUILDSTR}' -X 'main.versionString=${VERSION}' -X 'main.frontendDir=frontend/dist'" cmd/*.go
|
||||||
|
|
||||||
# Build the JS frontend into frontend/dist.
|
# Build the JS frontend into frontend/dist.
|
||||||
$(FRONTEND_DIST): $(FRONTEND_DEPS)
|
$(FRONTEND_DIST): $(FRONTEND_DEPS)
|
||||||
export VUE_APP_VERSION="${VERSION}" && cd frontend && $(YARN) build && mv dist/favicon.png dist/frontend/favicon.png
|
export VUE_APP_VERSION="${VERSION}" && cd frontend && $(YARN) build
|
||||||
touch --no-create $(FRONTEND_DIST)
|
touch --no-create $(FRONTEND_DIST)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
@ -37,7 +38,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// registerHandlers registers HTTP handlers.
|
// registerHandlers registers HTTP handlers.
|
||||||
func registerHTTPHandlers(e *echo.Echo, app *App) {
|
func initHTTPHandlers(e *echo.Echo, app *App) {
|
||||||
// Group of private handlers with BasicAuth.
|
// Group of private handlers with BasicAuth.
|
||||||
var g *echo.Group
|
var g *echo.Group
|
||||||
|
|
||||||
|
@ -48,7 +49,15 @@ func registerHTTPHandlers(e *echo.Echo, app *App) {
|
||||||
g = e.Group("", middleware.BasicAuth(basicAuth))
|
g = e.Group("", middleware.BasicAuth(basicAuth))
|
||||||
}
|
}
|
||||||
|
|
||||||
g.GET("/", handleIndexPage)
|
// Admin JS app views.
|
||||||
|
// /admin/static/* file server is registered in initHTTPServer().
|
||||||
|
g.GET("/", func(c echo.Context) error {
|
||||||
|
return c.Redirect(http.StatusPermanentRedirect, path.Join(adminRoot, ""))
|
||||||
|
})
|
||||||
|
g.GET(path.Join(adminRoot, ""), handleAdminPage)
|
||||||
|
g.GET(path.Join(adminRoot, "/*"), handleAdminPage)
|
||||||
|
|
||||||
|
// API endpoints.
|
||||||
g.GET("/api/health", handleHealthCheck)
|
g.GET("/api/health", handleHealthCheck)
|
||||||
g.GET("/api/config", handleGetServerConfig)
|
g.GET("/api/config", handleGetServerConfig)
|
||||||
g.GET("/api/lang/:lang", handleGetI18nLang)
|
g.GET("/api/lang/:lang", handleGetI18nLang)
|
||||||
|
@ -125,21 +134,6 @@ func registerHTTPHandlers(e *echo.Echo, app *App) {
|
||||||
g.PUT("/api/templates/:id/default", handleTemplateSetDefault)
|
g.PUT("/api/templates/:id/default", handleTemplateSetDefault)
|
||||||
g.DELETE("/api/templates/:id", handleDeleteTemplate)
|
g.DELETE("/api/templates/:id", handleDeleteTemplate)
|
||||||
|
|
||||||
// Static admin views.
|
|
||||||
g.GET("/lists", handleIndexPage)
|
|
||||||
g.GET("/lists/forms", handleIndexPage)
|
|
||||||
g.GET("/subscribers", handleIndexPage)
|
|
||||||
g.GET("/subscribers/lists/:listID", handleIndexPage)
|
|
||||||
g.GET("/subscribers/import", handleIndexPage)
|
|
||||||
g.GET("/subscribers/bounces", handleIndexPage)
|
|
||||||
g.GET("/campaigns", handleIndexPage)
|
|
||||||
g.GET("/campaigns/new", handleIndexPage)
|
|
||||||
g.GET("/campaigns/media", handleIndexPage)
|
|
||||||
g.GET("/campaigns/templates", handleIndexPage)
|
|
||||||
g.GET("/campaigns/:campignID", handleIndexPage)
|
|
||||||
g.GET("/settings", handleIndexPage)
|
|
||||||
g.GET("/settings/logs", handleIndexPage)
|
|
||||||
|
|
||||||
if app.constants.BounceWebhooksEnabled {
|
if app.constants.BounceWebhooksEnabled {
|
||||||
// Private authenticated bounce endpoint.
|
// Private authenticated bounce endpoint.
|
||||||
g.POST("/webhooks/bounce", handleBounceWebhook)
|
g.POST("/webhooks/bounce", handleBounceWebhook)
|
||||||
|
@ -171,17 +165,16 @@ func registerHTTPHandlers(e *echo.Echo, app *App) {
|
||||||
e.GET("/health", handleHealthCheck)
|
e.GET("/health", handleHealthCheck)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleIndex is the root handler that renders the Javascript frontend.
|
// handleAdminPage is the root handler that renders the Javascript admin frontend.
|
||||||
func handleIndexPage(c echo.Context) error {
|
func handleAdminPage(c echo.Context) error {
|
||||||
app := c.Get("app").(*App)
|
app := c.Get("app").(*App)
|
||||||
|
|
||||||
b, err := app.fs.Read("/frontend/index.html")
|
b, err := app.fs.Read(path.Join(adminRoot, "/index.html"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Response().Header().Set("Content-Type", "text/html")
|
return c.HTMLBlob(http.StatusOK, b)
|
||||||
return c.String(http.StatusOK, string(b))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleHealthCheck is a healthcheck endpoint that returns a 200 response.
|
// handleHealthCheck is a healthcheck endpoint that returns a 200 response.
|
||||||
|
|
19
cmd/init.go
19
cmd/init.go
|
@ -39,6 +39,9 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
queryFilePath = "queries.sql"
|
queryFilePath = "queries.sql"
|
||||||
|
|
||||||
|
// Root URI of the admin frontend.
|
||||||
|
adminRoot = "/admin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// constants contains static, constant config values required by the app.
|
// constants contains static, constant config values required by the app.
|
||||||
|
@ -129,9 +132,9 @@ func initFS(appDir, frontendDir, staticDir, i18nDir string) stuffbin.FileSystem
|
||||||
}
|
}
|
||||||
|
|
||||||
frontendFiles = []string{
|
frontendFiles = []string{
|
||||||
// The app's frontend assets are accessible at /frontend/js/* during runtime.
|
// Admin frontend's static assets accessible at /admin/* during runtime.
|
||||||
// These paths are joined with frontendDir.
|
// These paths are sourced from frontendDir.
|
||||||
"./:/frontend",
|
"./:/admin",
|
||||||
}
|
}
|
||||||
|
|
||||||
staticFiles = []string{
|
staticFiles = []string{
|
||||||
|
@ -574,15 +577,21 @@ func initHTTPServer(app *App) *echo.Echo {
|
||||||
|
|
||||||
// Initialize the static file server.
|
// Initialize the static file server.
|
||||||
fSrv := app.fs.FileServer()
|
fSrv := app.fs.FileServer()
|
||||||
|
|
||||||
|
// Public (subscriber) facing static files.
|
||||||
srv.GET("/public/*", echo.WrapHandler(fSrv))
|
srv.GET("/public/*", echo.WrapHandler(fSrv))
|
||||||
srv.GET("/frontend/*", echo.WrapHandler(fSrv))
|
|
||||||
|
// Admin (frontend) facing static files.
|
||||||
|
srv.GET("/admin/static/*", echo.WrapHandler(fSrv))
|
||||||
|
|
||||||
|
// Public (subscriber) facing media upload files.
|
||||||
if ko.String("upload.provider") == "filesystem" {
|
if ko.String("upload.provider") == "filesystem" {
|
||||||
srv.Static(ko.String("upload.filesystem.upload_uri"),
|
srv.Static(ko.String("upload.filesystem.upload_uri"),
|
||||||
ko.String("upload.filesystem.upload_path"))
|
ko.String("upload.filesystem.upload_path"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register all HTTP handlers.
|
// Register all HTTP handlers.
|
||||||
registerHTTPHandlers(srv, app)
|
initHTTPHandlers(srv, app)
|
||||||
|
|
||||||
// Start the server.
|
// Start the server.
|
||||||
go func() {
|
go func() {
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||||
<link rel="icon" href="<%= BASE_URL %>frontend/favicon.png" />
|
<link rel="icon" href="<%= BASE_URL %>static/favicon.png" />
|
||||||
<link href="https://fonts.googleapis.com/css?family=Inter:400,600" rel="stylesheet" />
|
<link href="https://fonts.googleapis.com/css?family=Inter:400,600" rel="stylesheet" />
|
||||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||||
</head>
|
</head>
|
||||||
|
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
@ -6,7 +6,7 @@ import store from '../store';
|
||||||
import { models } from '../constants';
|
import { models } from '../constants';
|
||||||
|
|
||||||
const http = axios.create({
|
const http = axios.create({
|
||||||
baseURL: process.env.BASE_URL,
|
baseURL: process.env.VUE_APP_API_URL || '/',
|
||||||
withCredentials: false,
|
withCredentials: false,
|
||||||
responseType: 'json',
|
responseType: 'json',
|
||||||
|
|
||||||
|
|
8
frontend/vue.config.js
vendored
8
frontend/vue.config.js
vendored
|
@ -1,5 +1,5 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
publicPath: '/',
|
publicPath: '/admin',
|
||||||
outputDir: 'dist',
|
outputDir: 'dist',
|
||||||
|
|
||||||
// This is to make all static file requests generated by Vue to go to
|
// This is to make all static file requests generated by Vue to go to
|
||||||
|
@ -7,10 +7,10 @@ module.exports = {
|
||||||
// directory and moves all the static files in it. The physical directory
|
// directory and moves all the static files in it. The physical directory
|
||||||
// and the URI for assets are tightly coupled. This is handled in the Go app
|
// and the URI for assets are tightly coupled. This is handled in the Go app
|
||||||
// by using stuffbin aliases.
|
// by using stuffbin aliases.
|
||||||
assetsDir: 'frontend',
|
assetsDir: 'static',
|
||||||
|
|
||||||
// Move the index.html file from dist/index.html to dist/frontend/index.html
|
// Move the index.html file from dist/index.html to dist/frontend/index.html
|
||||||
indexPath: './frontend/index.html',
|
// indexPath: './frontend/index.html',
|
||||||
|
|
||||||
productionSourceMap: false,
|
productionSourceMap: false,
|
||||||
filenameHashing: true,
|
filenameHashing: true,
|
||||||
|
@ -26,7 +26,7 @@ module.exports = {
|
||||||
devServer: {
|
devServer: {
|
||||||
port: process.env.LISTMONK_FRONTEND_PORT || 8080,
|
port: process.env.LISTMONK_FRONTEND_PORT || 8080,
|
||||||
proxy: {
|
proxy: {
|
||||||
'^/(api|webhooks|subscription|public)': {
|
'^/(api|webhooks|subscription|public)|$': {
|
||||||
target: process.env.LISTMONK_API_URL || 'http://127.0.0.1:9000'
|
target: process.env.LISTMONK_API_URL || 'http://127.0.0.1:9000'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue