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 restart EcoDash.
" +
"If you're running via Docker, click here 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%d", 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"))
}