Add admin-settable MOTD, rewrite existing warning system to use it
Some checks failed
ecodash/pipeline/head There was a failure building this commit
Some checks failed
ecodash/pipeline/head There was a failure building this commit
This commit is contained in:
parent
4bf1455ba4
commit
8b81c41bd7
|
@ -4,6 +4,7 @@ import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"html/template"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
@ -37,10 +38,17 @@ type Administrator struct {
|
||||||
PasswordHash string `json:"password_hash"`
|
PasswordHash string `json:"password_hash"`
|
||||||
}
|
}
|
||||||
type Dashboard struct {
|
type Dashboard struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Theme string `json:"theme"`
|
Theme string `json:"theme"`
|
||||||
FooterLinks []Link `json:"footer_links"`
|
FooterLinks []Link `json:"footer_links"`
|
||||||
HeaderLinks []Link `json:"header_links"`
|
HeaderLinks []Link `json:"header_links"`
|
||||||
|
MOTD *MessageCard `json:"motd"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MessageCard struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Content template.HTML `json:"content"`
|
||||||
|
Style string `json:"style"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var errBadHAFormat = errors.New("HomeAssistant base URL is badly formatted")
|
var errBadHAFormat = errors.New("HomeAssistant base URL is badly formatted")
|
||||||
|
|
|
@ -47,16 +47,17 @@ func (config *Config) AdminEndpoint(c *fiber.Ctx) error {
|
||||||
if config.IsAuthorized(c) { // here the user is submitting the form to change configuration
|
if config.IsAuthorized(c) { // here the user is submitting the form to change configuration
|
||||||
err := config.saveAdminForm(c)
|
err := config.saveAdminForm(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return config.RenderAdminPanel(c, Warning{
|
return config.RenderAdminPanel(c, &MessageCard{
|
||||||
Header: "An error occurred!",
|
Title: "An error occurred!",
|
||||||
Body: html.EscapeString(err.Error()),
|
Content: template.HTML(html.EscapeString(err.Error())),
|
||||||
|
Style: "error",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return config.RenderAdminPanel(c, Warning{
|
return config.RenderAdminPanel(c, &MessageCard{
|
||||||
Header: "Settings applied",
|
Title: "Settings applied",
|
||||||
Body: "Your settings have been tested and <b>applied successfully</b>.<br>" +
|
Content: "Your settings have been tested and <b>applied successfully</b>.<br>" +
|
||||||
"You can continue using EcoDash on the <a href='./'>Home</a>.",
|
"You can continue using EcoDash on the <a href='./'>Home</a>.",
|
||||||
IsSuccess: true,
|
Style: "success",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,38 +65,28 @@ func (config *Config) AdminEndpoint(c *fiber.Ctx) error {
|
||||||
if c.FormValue("username") == config.Administrator.Username && tools.Hash(c.FormValue("password")) == config.Administrator.PasswordHash {
|
if c.FormValue("username") == config.Administrator.Username && tools.Hash(c.FormValue("password")) == config.Administrator.PasswordHash {
|
||||||
c.Cookie(&fiber.Cookie{Name: "admin_username", Value: c.FormValue("username")})
|
c.Cookie(&fiber.Cookie{Name: "admin_username", Value: c.FormValue("username")})
|
||||||
c.Cookie(&fiber.Cookie{Name: "admin_password_hash", Value: tools.Hash(c.FormValue("password"))})
|
c.Cookie(&fiber.Cookie{Name: "admin_password_hash", Value: tools.Hash(c.FormValue("password"))})
|
||||||
return config.RenderAdminPanel(c)
|
return config.RenderAdminPanel(c, nil)
|
||||||
}
|
}
|
||||||
return c.Render("login", fiber.Map{"Defaults": config.getTemplateDefaults(), "Failed": true}, "base")
|
return c.Render("login", fiber.Map{"Defaults": config.getTemplateDefaults(), "Failed": true}, "base")
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.IsAuthorized(c) {
|
if config.IsAuthorized(c) {
|
||||||
return config.RenderAdminPanel(c)
|
return config.RenderAdminPanel(c, nil)
|
||||||
}
|
}
|
||||||
return c.Render("login", config.TemplateDefaultsMap(), "base")
|
return c.Render("login", config.TemplateDefaultsMap(), "base")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (config *Config) RenderAdminPanel(c *fiber.Ctx, warning ...Warning) error {
|
func (config *Config) RenderAdminPanel(c *fiber.Ctx, message *MessageCard) error {
|
||||||
dirs, err := os.ReadDir("./templates")
|
dirs, err := os.ReadDir("./templates")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(warning) > 0 {
|
|
||||||
// #nosec // TODO this is dangerous, even if we're escaping the only place where we're passing a non-literal
|
|
||||||
warning[0].BodyHTML = template.HTML(warning[0].Body)
|
|
||||||
return c.Render("admin", fiber.Map{
|
|
||||||
"Defaults": config.getTemplateDefaults(),
|
|
||||||
"Themes": dirs,
|
|
||||||
"Config": config,
|
|
||||||
"Warning": warning[0],
|
|
||||||
}, "base")
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Render("admin", fiber.Map{
|
return c.Render("admin", fiber.Map{
|
||||||
"Defaults": config.getTemplateDefaults(),
|
"Defaults": config.getTemplateDefaults(),
|
||||||
"Themes": dirs,
|
"Themes": dirs,
|
||||||
"Config": config,
|
"Config": config,
|
||||||
|
"Message": message,
|
||||||
}, "base")
|
}, "base")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,7 +112,7 @@ func (config *Config) saveAdminForm(c *fiber.Ctx) error {
|
||||||
HomeAssistant: HomeAssistant{ /*BaseURL to be filled later*/ APIKey: c.FormValue("api_key"), InstallationDate: dayStart(parsedTime)},
|
HomeAssistant: HomeAssistant{ /*BaseURL to be filled later*/ APIKey: c.FormValue("api_key"), InstallationDate: dayStart(parsedTime)},
|
||||||
Sensors: Sensors{PolledSmartEnergySummation: c.FormValue("polled_smart_energy_summation"), FossilPercentage: c.FormValue("fossil_percentage")},
|
Sensors: Sensors{PolledSmartEnergySummation: c.FormValue("polled_smart_energy_summation"), FossilPercentage: c.FormValue("fossil_percentage")},
|
||||||
Administrator: Administrator{Username: c.FormValue("username") /*PasswordHash to be filled later*/},
|
Administrator: Administrator{Username: c.FormValue("username") /*PasswordHash to be filled later*/},
|
||||||
Dashboard: Dashboard{Theme: c.FormValue("theme"), Name: c.FormValue("name"), HeaderLinks: config.Dashboard.HeaderLinks, FooterLinks: config.Dashboard.FooterLinks},
|
Dashboard: Dashboard{Theme: c.FormValue("theme"), Name: c.FormValue("name"), HeaderLinks: config.Dashboard.HeaderLinks, FooterLinks: config.Dashboard.FooterLinks /*MessageCard to be filled later*/},
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.FormValue("keep_old_password") == "" {
|
if c.FormValue("keep_old_password") == "" {
|
||||||
|
@ -130,6 +121,14 @@ func (config *Config) saveAdminForm(c *fiber.Ctx) error {
|
||||||
form.Administrator.PasswordHash = config.Administrator.PasswordHash
|
form.Administrator.PasswordHash = config.Administrator.PasswordHash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.FormValue("motd_title") != "" || c.FormValue("motd_content") != "" {
|
||||||
|
form.Dashboard.MOTD = &MessageCard{
|
||||||
|
Title: c.FormValue("motd_title"),
|
||||||
|
Content: template.HTML(c.FormValue("motd_content")),
|
||||||
|
Style: c.FormValue("motd_style"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fmtURL, err := formatURL(c.FormValue("base_url"))
|
fmtURL, err := formatURL(c.FormValue("base_url"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -202,5 +201,6 @@ func (config *Config) RenderIndex(c *fiber.Ctx) error {
|
||||||
"EnergyConsumptions": energyConsumptions,
|
"EnergyConsumptions": energyConsumptions,
|
||||||
"GreenEnergyPercent": averageExcludingCurrentDay(greenEnergyPercents),
|
"GreenEnergyPercent": averageExcludingCurrentDay(greenEnergyPercents),
|
||||||
"PerDayUsage": perDayUsage,
|
"PerDayUsage": perDayUsage,
|
||||||
|
"MOTD": config.Dashboard.MOTD,
|
||||||
}, "base")
|
}, "base")
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ func main() {
|
||||||
if config.Administrator.Username == "" || config.Administrator.PasswordHash == "" {
|
if config.Administrator.Username == "" || config.Administrator.PasswordHash == "" {
|
||||||
c.Cookie(&fiber.Cookie{Name: "admin_username", Value: ""})
|
c.Cookie(&fiber.Cookie{Name: "admin_username", Value: ""})
|
||||||
c.Cookie(&fiber.Cookie{Name: "admin_password_hash", Value: tools.Hash("")})
|
c.Cookie(&fiber.Cookie{Name: "admin_password_hash", Value: tools.Hash("")})
|
||||||
return config.RenderAdminPanel(c)
|
return config.RenderAdminPanel(c, nil)
|
||||||
}
|
}
|
||||||
return config.RenderIndex(c)
|
return config.RenderIndex(c)
|
||||||
})
|
})
|
||||||
|
|
|
@ -4,15 +4,15 @@
|
||||||
<a href="https://ecodash.xyz/docs/setup/admin-panel">Documentation</a>
|
<a href="https://ecodash.xyz/docs/setup/admin-panel">Documentation</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{{if .Warning}}
|
{{if .Message}}
|
||||||
<article class="card" style="background-color: {{if .Warning.IsSuccess}}#008000{{else}}#ff5050{{end}}; color: white">
|
<article class="card {{.Message.Style}}">
|
||||||
<header>
|
<header>
|
||||||
<h3>{{.Warning.Header}}</h3>
|
<h3>{{.Message.Title}}</h3>
|
||||||
</header>
|
</header>
|
||||||
<footer>
|
<footer>
|
||||||
<p>{{.Warning.BodyHTML}}</p>
|
<p>{{.Message.Content}}</p>
|
||||||
</footer>
|
</footer>
|
||||||
</article>
|
</article>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
<form action="./admin" method="POST">
|
<form action="./admin" method="POST">
|
||||||
|
@ -45,6 +45,16 @@
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<label>Dashboard name <input type="text" name="name" value="{{.Config.Dashboard.Name}}"></label>
|
<label>Dashboard name <input type="text" name="name" value="{{.Config.Dashboard.Name}}"></label>
|
||||||
|
<label>MOTD title <input type="text" name="motd_title" value="{{if .Config.Dashboard.MOTD}}{{.Config.Dashboard.MOTD.Title}}{{end}}"></label>
|
||||||
|
<label>MOTD content <input type="text" name="motd_content" value="{{if .Config.Dashboard.MOTD}}{{.Config.Dashboard.MOTD.Content}}{{end}}"></label>
|
||||||
|
<label>MOTD style
|
||||||
|
<select name="motd_style">
|
||||||
|
<option value="" {{if .Config.Dashboard.MOTD}}{{if eq .Config.Dashboard.MOTD.Style ""}}selected{{end}}{{end}}>Default</option>
|
||||||
|
<option value="success" {{if .Config.Dashboard.MOTD}}{{if eq .Config.Dashboard.MOTD.Style "success"}}selected{{end}}{{end}}>Success</option>
|
||||||
|
<option value="warning" {{if .Config.Dashboard.MOTD}}{{if eq .Config.Dashboard.MOTD.Style "warning"}}selected{{end}}{{end}}>Warning</option>
|
||||||
|
<option value="error" {{if .Config.Dashboard.MOTD}}{{if eq .Config.Dashboard.MOTD.Style "error"}}selected{{end}}{{end}}>Error</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
<input type="submit" placeholder="Submit" style="margin-top: 2em; width: 100%">
|
<input type="submit" placeholder="Submit" style="margin-top: 2em; width: 100%">
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -46,4 +46,14 @@ svg, footer img { width: 100% }
|
||||||
fill: white;
|
fill: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.success {
|
||||||
|
background-color: #008000; color: white
|
||||||
|
}
|
||||||
|
.warning {
|
||||||
|
background-color: #807a00; color: white
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
background-color: #800000; color: white
|
||||||
}
|
}
|
|
@ -1,5 +1,16 @@
|
||||||
<script src="assets/chartjs/chart.js"></script>
|
<script src="assets/chartjs/chart.js"></script>
|
||||||
|
|
||||||
|
{{if .MOTD}}
|
||||||
|
<article class="card {{.MOTD.Style}}">
|
||||||
|
<header>
|
||||||
|
<h3>{{.MOTD.Title}}</h3>
|
||||||
|
</header>
|
||||||
|
<footer>
|
||||||
|
<p>{{.MOTD.Content}}</p>
|
||||||
|
</footer>
|
||||||
|
</article>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
<h1>Green report</h1>
|
<h1>Green report</h1>
|
||||||
|
|
||||||
<canvas id="report"></canvas>
|
<canvas id="report"></canvas>
|
||||||
|
@ -7,7 +18,6 @@
|
||||||
This server's energy statistics for the last eight days (current day included)
|
This server's energy statistics for the last eight days (current day included)
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
<div class="flex two">
|
<div class="flex two">
|
||||||
<div>
|
<div>
|
||||||
<div class="home-cards card">
|
<div class="home-cards card">
|
||||||
|
|
Loading…
Reference in a new issue