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")) }