Load global configuration into the frontend as a JS dict using a <script> inclusion
This commit is contained in:
parent
09b7fc8d0c
commit
ad8787cab3
30
admin.go
30
admin.go
|
@ -1,13 +1,43 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/labstack/echo"
|
"github.com/labstack/echo"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type configScript struct {
|
||||||
|
RootURL string `json:"rootURL"`
|
||||||
|
UploadURI string `json:"uploadURL"`
|
||||||
|
FromEmail string `json:"fromEmail"`
|
||||||
|
Messengers []string `json:"messengers"`
|
||||||
|
}
|
||||||
|
|
||||||
// handleGetStats returns a collection of general statistics.
|
// handleGetStats returns a collection of general statistics.
|
||||||
func handleGetStats(c echo.Context) error {
|
func handleGetStats(c echo.Context) error {
|
||||||
app := c.Get("app").(*App)
|
app := c.Get("app").(*App)
|
||||||
return c.JSON(http.StatusOK, okResp{app.Runner.GetMessengerNames()})
|
return c.JSON(http.StatusOK, okResp{app.Runner.GetMessengerNames()})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleGetConfigScript returns general configuration as a Javascript
|
||||||
|
// variable that can be included in an HTML page directly.
|
||||||
|
func handleGetConfigScript(c echo.Context) error {
|
||||||
|
var (
|
||||||
|
app = c.Get("app").(*App)
|
||||||
|
out = configScript{
|
||||||
|
RootURL: app.Constants.RootURL,
|
||||||
|
UploadURI: app.Constants.UploadURI,
|
||||||
|
FromEmail: app.Constants.FromEmail,
|
||||||
|
Messengers: app.Runner.GetMessengerNames(),
|
||||||
|
}
|
||||||
|
|
||||||
|
b = bytes.Buffer{}
|
||||||
|
j = json.NewEncoder(&b)
|
||||||
|
)
|
||||||
|
|
||||||
|
b.Write([]byte(`var CONFIG = `))
|
||||||
|
j.Encode(out)
|
||||||
|
return c.Blob(http.StatusOK, "application/javascript", b.Bytes())
|
||||||
|
}
|
||||||
|
|
|
@ -401,12 +401,6 @@ func handleGetRunningCampaignStats(c echo.Context) error {
|
||||||
return c.JSON(http.StatusOK, okResp{out})
|
return c.JSON(http.StatusOK, okResp{out})
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleGetCampaignMessengers returns the list of registered messengers.
|
|
||||||
func handleGetCampaignMessengers(c echo.Context) error {
|
|
||||||
app := c.Get("app").(*App)
|
|
||||||
return c.JSON(http.StatusOK, okResp{app.Runner.GetMessengerNames()})
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleTestCampaign handles the sending of a campaign message to
|
// handleTestCampaign handles the sending of a campaign message to
|
||||||
// arbitrary subscribers for testing.
|
// arbitrary subscribers for testing.
|
||||||
func handleTestCampaign(c echo.Context) error {
|
func handleTestCampaign(c echo.Context) error {
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
# Interface and port where the app will run its webserver.
|
# Interface and port where the app will run its webserver.
|
||||||
address = "0.0.0.0:9000"
|
address = "0.0.0.0:9000"
|
||||||
|
|
||||||
# Root to the listmonk installation that'll be used in the e-mails for linking to
|
# Public root URL of the listmonk installation that'll be used
|
||||||
# images, the unsubscribe URL etc.
|
# in the messages for linking to images, unsubscribe page etc.
|
||||||
root = "http://listmonk.mysite.com"
|
root = "http://listmonk.mysite.com"
|
||||||
|
|
||||||
# The default 'from' e-mail for outgoing e-mail campaigns.
|
# The default 'from' e-mail for outgoing e-mail campaigns.
|
||||||
|
|
|
@ -4,21 +4,9 @@
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
<meta name="theme-color" content="#000000">
|
<meta name="theme-color" content="#000000">
|
||||||
<!--
|
<script src="%PUBLIC_URL%/api/config.js" type="text/javascript"></script>
|
||||||
manifest.json provides metadata used when your web app is added to the
|
|
||||||
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
|
|
||||||
-->
|
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
|
||||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||||
<!--
|
|
||||||
Notice the use of %PUBLIC_URL% in the tags above.
|
|
||||||
It will be replaced with the URL of the `public` folder during the build.
|
|
||||||
Only files inside the `public` folder can be referenced from the HTML.
|
|
||||||
|
|
||||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
|
||||||
work correctly both with client-side routing and a non-root public URL.
|
|
||||||
Learn how to configure a non-root public URL by running `npm run build`.
|
|
||||||
-->
|
|
||||||
<title>React App</title>
|
<title>React App</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
|
@ -118,6 +118,7 @@ class App extends React.PureComponent {
|
||||||
<Layout modelRequest={ this.modelRequest }
|
<Layout modelRequest={ this.modelRequest }
|
||||||
request={ this.request }
|
request={ this.request }
|
||||||
reqStates={ this.state.reqStates }
|
reqStates={ this.state.reqStates }
|
||||||
|
config={ window.CONFIG }
|
||||||
data={ this.state.data } />
|
data={ this.state.data } />
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
)
|
)
|
||||||
|
|
|
@ -298,7 +298,7 @@ class TheFormDef extends React.PureComponent {
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item {...formItemLayout} label="From address">
|
<Form.Item {...formItemLayout} label="From address">
|
||||||
{getFieldDecorator("from_email", {
|
{getFieldDecorator("from_email", {
|
||||||
initialValue: record.from_email,
|
initialValue: record.from_email ? record.from_email : this.props.config.fromEmail,
|
||||||
rules: [{ required: true }, { validator: this.validateEmail }]
|
rules: [{ required: true }, { validator: this.validateEmail }]
|
||||||
})(<Input disabled={ this.props.formDisabled } placeholder="Company Name <email@company.com>" maxLength="200" />)}
|
})(<Input disabled={ this.props.formDisabled } placeholder="Company Name <email@company.com>" maxLength="200" />)}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
@ -325,10 +325,10 @@ class TheFormDef extends React.PureComponent {
|
||||||
<Select disabled={ this.props.formDisabled } mode="tags"></Select>
|
<Select disabled={ this.props.formDisabled } mode="tags"></Select>
|
||||||
)}
|
)}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item {...formItemLayout} label="Messenger">
|
<Form.Item {...formItemLayout} label="Messenger" style={{ display: this.props.config.messengers.length === 1 ? "none" : "block" }}>
|
||||||
{getFieldDecorator("messenger", { initialValue: record.messenger ? record.messenger : "email" })(
|
{getFieldDecorator("messenger", { initialValue: record.messenger ? record.messenger : "email" })(
|
||||||
<Radio.Group className="messengers">
|
<Radio.Group className="messengers">
|
||||||
{[...this.props.messengers].map((v, i) =>
|
{[...this.props.config.messengers].map((v, i) =>
|
||||||
<Radio disabled={ this.props.formDisabled } value={v} key={v}>{ v }</Radio>
|
<Radio disabled={ this.props.formDisabled } value={v} key={v}>{ v }</Radio>
|
||||||
)}
|
)}
|
||||||
</Radio.Group>
|
</Radio.Group>
|
||||||
|
@ -395,7 +395,6 @@ class Campaign extends React.PureComponent {
|
||||||
campaignID: this.props.route.match.params ? parseInt(this.props.route.match.params.campaignID, 10) : 0,
|
campaignID: this.props.route.match.params ? parseInt(this.props.route.match.params.campaignID, 10) : 0,
|
||||||
record: {},
|
record: {},
|
||||||
contentType: "richtext",
|
contentType: "richtext",
|
||||||
messengers: [],
|
|
||||||
previewRecord: null,
|
previewRecord: null,
|
||||||
body: "",
|
body: "",
|
||||||
currentTab: "form",
|
currentTab: "form",
|
||||||
|
@ -412,14 +411,11 @@ class Campaign extends React.PureComponent {
|
||||||
// Fetch templates.
|
// Fetch templates.
|
||||||
this.props.modelRequest(cs.ModelTemplates, cs.Routes.GetTemplates, cs.MethodGet)
|
this.props.modelRequest(cs.ModelTemplates, cs.Routes.GetTemplates, cs.MethodGet)
|
||||||
|
|
||||||
// Fetch messengers.
|
|
||||||
this.props.request(cs.Routes.GetCampaignMessengers, cs.MethodGet).then((r) => {
|
|
||||||
this.setState({ messengers: r.data.data, loading: false })
|
|
||||||
})
|
|
||||||
|
|
||||||
// Fetch campaign.
|
// Fetch campaign.
|
||||||
if(this.state.campaignID) {
|
if(this.state.campaignID) {
|
||||||
this.fetchRecord(this.state.campaignID)
|
this.fetchRecord(this.state.campaignID)
|
||||||
|
} else {
|
||||||
|
this.setState({ loading: false })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -482,7 +478,6 @@ class Campaign extends React.PureComponent {
|
||||||
<TheForm { ...this.props }
|
<TheForm { ...this.props }
|
||||||
record={ this.state.record }
|
record={ this.state.record }
|
||||||
isSingle={ this.state.record.id ? true : false }
|
isSingle={ this.state.record.id ? true : false }
|
||||||
messengers={ this.state.messengers }
|
|
||||||
body={ this.state.body ? this.state.body : this.state.record.body }
|
body={ this.state.body ? this.state.body : this.state.record.body }
|
||||||
contentType={ this.state.contentType }
|
contentType={ this.state.contentType }
|
||||||
formDisabled={ this.state.formDisabled }
|
formDisabled={ this.state.formDisabled }
|
||||||
|
|
3
main.go
3
main.go
|
@ -26,6 +26,7 @@ type constants struct {
|
||||||
RootURL string `mapstructure:"root"`
|
RootURL string `mapstructure:"root"`
|
||||||
UploadPath string `mapstructure:"upload_path"`
|
UploadPath string `mapstructure:"upload_path"`
|
||||||
UploadURI string `mapstructure:"upload_uri"`
|
UploadURI string `mapstructure:"upload_uri"`
|
||||||
|
FromEmail string `mapstructure:"from_email"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// App contains the "global" components that are
|
// App contains the "global" components that are
|
||||||
|
@ -77,6 +78,7 @@ func init() {
|
||||||
// registerHandlers registers HTTP handlers.
|
// registerHandlers registers HTTP handlers.
|
||||||
func registerHandlers(e *echo.Echo) {
|
func registerHandlers(e *echo.Echo) {
|
||||||
e.GET("/", handleIndexPage)
|
e.GET("/", handleIndexPage)
|
||||||
|
e.GET("/api/config.js", handleGetConfigScript)
|
||||||
e.GET("/api/users", handleGetUsers)
|
e.GET("/api/users", handleGetUsers)
|
||||||
e.POST("/api/users", handleCreateUser)
|
e.POST("/api/users", handleCreateUser)
|
||||||
e.DELETE("/api/users/:id", handleDeleteUser)
|
e.DELETE("/api/users/:id", handleDeleteUser)
|
||||||
|
@ -102,7 +104,6 @@ func registerHandlers(e *echo.Echo) {
|
||||||
|
|
||||||
e.GET("/api/campaigns", handleGetCampaigns)
|
e.GET("/api/campaigns", handleGetCampaigns)
|
||||||
e.GET("/api/campaigns/running/stats", handleGetRunningCampaignStats)
|
e.GET("/api/campaigns/running/stats", handleGetRunningCampaignStats)
|
||||||
e.GET("/api/campaigns/messengers", handleGetCampaignMessengers)
|
|
||||||
e.GET("/api/campaigns/:id", handleGetCampaigns)
|
e.GET("/api/campaigns/:id", handleGetCampaigns)
|
||||||
e.GET("/api/campaigns/:id/preview", handlePreviewCampaign)
|
e.GET("/api/campaigns/:id/preview", handlePreviewCampaign)
|
||||||
e.POST("/api/campaigns/:id/preview", handlePreviewCampaign)
|
e.POST("/api/campaigns/:id/preview", handlePreviewCampaign)
|
||||||
|
|
Loading…
Reference in New Issue