forked from massivebox/ecodash
MassiveBox
52ba0ea4c1
- Now the cache isn't cleared and fetched from zero each update, but only when there's missing information for a day - Fixed a bug that prevented new users from saving changes in the config NOTE: Turns out that HomeAssistant's API only returns data for the last 10 days. Looks like we will have to improve the caching system to work around this.
232 lines
6.4 KiB
Go
232 lines
6.4 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"github.com/gofiber/fiber/v2"
|
|
"html/template"
|
|
"math"
|
|
"os"
|
|
"reflect"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
type Link struct {
|
|
Label string `json:"label"`
|
|
Destination string `json:"destination"`
|
|
NewTab bool `json:"new_tab"`
|
|
Primary bool `json:"primary"`
|
|
}
|
|
|
|
type Warning struct {
|
|
Header string
|
|
Body string
|
|
BodyHTML template.HTML
|
|
IsSuccess bool
|
|
}
|
|
|
|
func (config Config) getTemplateDefaults() fiber.Map {
|
|
return fiber.Map{
|
|
"DashboardName": config.Dashboard.Name,
|
|
"HeaderLinks": config.Dashboard.HeaderLinks,
|
|
"FooterLinks": config.Dashboard.FooterLinks,
|
|
}
|
|
}
|
|
|
|
func (config Config) templateDefaultsMap() fiber.Map {
|
|
return fiber.Map{
|
|
"Default": config.getTemplateDefaults(),
|
|
}
|
|
}
|
|
|
|
func (config Config) adminEndpoint(c *fiber.Ctx) error {
|
|
|
|
if c.Method() == "POST" {
|
|
|
|
if config.isAuthorized(c) { // here the user is submitting the form to change configuration
|
|
err := config.saveAdminForm(c)
|
|
if err != nil {
|
|
return config.renderAdminPanel(c, Warning{
|
|
Header: "An error occurred!",
|
|
Body: err.Error(),
|
|
})
|
|
}
|
|
return config.renderAdminPanel(c, Warning{
|
|
Header: "Restart needed",
|
|
Body: "In order to apply changes, please <b>restart EcoDash</b>.<br>" +
|
|
"If you're running via Docker, click <a href='./restart'>here</a> to restart automatically.",
|
|
IsSuccess: true,
|
|
})
|
|
}
|
|
|
|
// here the user is trying to authenticate
|
|
if c.FormValue("username") == config.Administrator.Username && hash(c.FormValue("password")) == config.Administrator.PasswordHash {
|
|
c.Cookie(&fiber.Cookie{Name: "admin_username", Value: c.FormValue("username")})
|
|
c.Cookie(&fiber.Cookie{Name: "admin_password_hash", Value: hash(c.FormValue("password"))})
|
|
return config.renderAdminPanel(c)
|
|
}
|
|
return c.Render("login", fiber.Map{"Defaults": config.getTemplateDefaults(), "Failed": true}, "base")
|
|
|
|
}
|
|
|
|
if config.isAuthorized(c) {
|
|
return config.renderAdminPanel(c)
|
|
}
|
|
return c.Render("login", config.templateDefaultsMap(), "base")
|
|
|
|
}
|
|
|
|
func (config Config) renderAdminPanel(c *fiber.Ctx, warning ...Warning) error {
|
|
|
|
dirs, err := os.ReadDir("./templates")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(warning) > 0 {
|
|
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{
|
|
"Defaults": config.getTemplateDefaults(),
|
|
"Themes": dirs,
|
|
"Config": config,
|
|
}, "base")
|
|
|
|
}
|
|
|
|
func (config Config) saveAdminForm(c *fiber.Ctx) error {
|
|
|
|
requiredFields := []string{"base_url", "api_key", "polled_smart_energy_summation", "fossil_percentage", "username", "theme", "name", "installation_date"}
|
|
for _, requiredField := range requiredFields {
|
|
if c.FormValue(requiredField) == "" {
|
|
return errors.New("Required field is missing: " + requiredField)
|
|
}
|
|
}
|
|
|
|
parsedTime, err := time.Parse("2006-01-02", c.FormValue("installation_date"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
form := Config{
|
|
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")},
|
|
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},
|
|
}
|
|
|
|
if c.FormValue("keep_old_password") == "" {
|
|
form.Administrator.PasswordHash = hash(c.FormValue("password"))
|
|
} else {
|
|
form.Administrator.PasswordHash = config.Administrator.PasswordHash
|
|
}
|
|
|
|
fmtURL, err := formatURL(c.FormValue("base_url"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
form.HomeAssistant.BaseURL = fmtURL
|
|
|
|
if reflect.DeepEqual(form, config) {
|
|
return errors.New("No changes from previous config.")
|
|
}
|
|
|
|
form.db = config.db
|
|
err = form.refreshCacheFromInstall()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
js, err := json.Marshal(form)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return os.WriteFile("config.json", js, 0666)
|
|
|
|
}
|
|
|
|
func averageExcludingCurrentDay(data []float32) float32 {
|
|
if len(data) == 0 {
|
|
return 0
|
|
}
|
|
data = data[:len(data)-1]
|
|
var sum float32
|
|
for _, num := range data {
|
|
sum += num
|
|
}
|
|
var avg = sum / float32(len(data))
|
|
return float32(math.Floor(float64(avg)*100)) / 100
|
|
}
|
|
|
|
func (config Config) renderIndex(c *fiber.Ctx) error {
|
|
|
|
if config.HomeAssistant.InstallationDate.IsZero() {
|
|
return c.Render("config-error", fiber.Map{
|
|
"Defaults": config.getTemplateDefaults(),
|
|
"Error": "The installation date is not set! This is normal if you've just updated from v0.1 to v0.2.",
|
|
}, "base")
|
|
}
|
|
|
|
data, err := config.readCache()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var (
|
|
labels []string
|
|
greenEnergyConsumptionAbsolute []float32
|
|
greenEnergyPercents []float32
|
|
energyConsumptions []float32
|
|
)
|
|
|
|
for _, datum := range data {
|
|
labels = append(labels, time.Unix(datum.Date, 0).Format("02/01"))
|
|
greenEnergyPercents = append(greenEnergyPercents, datum.GreenEnergyPercentage)
|
|
greenEnergyConsumptionAbsolute = append(greenEnergyConsumptionAbsolute, datum.GreenEnergyPercentage/100*datum.PolledSmartEnergySummation)
|
|
energyConsumptions = append(energyConsumptions, datum.PolledSmartEnergySummation)
|
|
}
|
|
|
|
perDayUsage := averageExcludingCurrentDay(energyConsumptions)
|
|
|
|
return c.Render("index", fiber.Map{
|
|
"Defaults": config.getTemplateDefaults(),
|
|
"Labels": labels,
|
|
"GreenEnergyPercents": greenEnergyConsumptionAbsolute,
|
|
"EnergyConsumptions": energyConsumptions,
|
|
"GreenEnergyPercent": averageExcludingCurrentDay(greenEnergyPercents),
|
|
"PerDayUsage": perDayUsage,
|
|
}, "base")
|
|
|
|
}
|
|
|
|
func templateDivide(num1, num2 float32) template.HTML {
|
|
|
|
division := float64(num1 / num2)
|
|
|
|
powerOfTen := int(math.Floor(math.Log10(division)))
|
|
if powerOfTen >= -2 && powerOfTen <= 2 {
|
|
return template.HTML(fmt.Sprintf("%s", strconv.FormatFloat(math.Round(division*100)/100, 'f', -1, 64)))
|
|
}
|
|
|
|
preComma := division / math.Pow10(powerOfTen)
|
|
return template.HTML(fmt.Sprintf("%s * 10<sup>%d</sup>", strconv.FormatFloat(math.Round(preComma*100)/100, 'f', -1, 64), powerOfTen))
|
|
|
|
}
|
|
|
|
func templateHTMLDateFormat(date time.Time) template.HTML {
|
|
if date.IsZero() {
|
|
return ""
|
|
}
|
|
return template.HTML(date.Format("2006-01-02"))
|
|
}
|