2023-05-01 17:39:12 +00:00
package ecodash
2022-10-15 17:58:44 +00:00
import (
"encoding/json"
"errors"
"fmt"
2023-05-01 17:25:16 +00:00
"html"
2022-10-15 17:58:44 +00:00
"html/template"
"math"
"os"
"time"
2023-05-01 16:33:15 +00:00
2023-05-01 17:39:12 +00:00
"git.massivebox.net/ecodash/ecodash/src/tools"
2023-05-01 16:33:15 +00:00
"github.com/gofiber/fiber/v2"
2022-10-15 17:58:44 +00:00
)
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
}
2023-05-01 16:33:15 +00:00
func ( config * Config ) getTemplateDefaults ( ) fiber . Map {
2022-10-15 17:58:44 +00:00
return fiber . Map {
"DashboardName" : config . Dashboard . Name ,
"HeaderLinks" : config . Dashboard . HeaderLinks ,
"FooterLinks" : config . Dashboard . FooterLinks ,
}
}
2023-05-01 17:39:12 +00:00
func ( config * Config ) TemplateDefaultsMap ( ) fiber . Map {
2022-12-04 11:50:11 +00:00
return fiber . Map {
"Default" : config . getTemplateDefaults ( ) ,
}
}
2023-05-01 17:39:12 +00:00
func ( config * Config ) AdminEndpoint ( c * fiber . Ctx ) error {
2022-10-15 17:58:44 +00:00
if c . Method ( ) == "POST" {
2023-05-01 17:39:12 +00:00
if config . IsAuthorized ( c ) { // here the user is submitting the form to change configuration
2022-10-15 17:58:44 +00:00
err := config . saveAdminForm ( c )
if err != nil {
2023-11-03 21:53:54 +00:00
// #nosec the input is admin-defined, and the admin is assumed to be trusted.
2023-10-31 22:59:09 +00:00
return config . RenderAdminPanel ( c , & MessageCard {
Title : "An error occurred!" ,
Content : template . HTML ( html . EscapeString ( err . Error ( ) ) ) ,
Style : "error" ,
2022-10-15 17:58:44 +00:00
} )
}
2023-10-31 22:59:09 +00:00
return config . RenderAdminPanel ( c , & MessageCard {
Title : "Settings applied" ,
Content : "Your settings have been tested and <b>applied successfully</b>.<br>" +
2023-10-29 17:32:35 +00:00
"You can continue using EcoDash on the <a href='./'>Home</a>." ,
2023-10-31 22:59:09 +00:00
Style : "success" ,
2022-10-15 17:58:44 +00:00
} )
}
// here the user is trying to authenticate
2023-05-01 17:39:12 +00:00
if c . FormValue ( "username" ) == config . Administrator . Username && tools . Hash ( c . FormValue ( "password" ) ) == config . Administrator . PasswordHash {
2022-10-15 17:58:44 +00:00
c . Cookie ( & fiber . Cookie { Name : "admin_username" , Value : c . FormValue ( "username" ) } )
2023-05-01 17:39:12 +00:00
c . Cookie ( & fiber . Cookie { Name : "admin_password_hash" , Value : tools . Hash ( c . FormValue ( "password" ) ) } )
2023-10-31 22:59:09 +00:00
return config . RenderAdminPanel ( c , nil )
2022-10-15 17:58:44 +00:00
}
return c . Render ( "login" , fiber . Map { "Defaults" : config . getTemplateDefaults ( ) , "Failed" : true } , "base" )
}
2023-05-01 17:39:12 +00:00
if config . IsAuthorized ( c ) {
2023-10-31 22:59:09 +00:00
return config . RenderAdminPanel ( c , nil )
2022-10-15 17:58:44 +00:00
}
2023-05-01 17:39:12 +00:00
return c . Render ( "login" , config . TemplateDefaultsMap ( ) , "base" )
2022-10-15 17:58:44 +00:00
}
2023-10-31 22:59:09 +00:00
func ( config * Config ) RenderAdminPanel ( c * fiber . Ctx , message * MessageCard ) error {
2022-10-15 17:58:44 +00:00
dirs , err := os . ReadDir ( "./templates" )
if err != nil {
return err
}
return c . Render ( "admin" , fiber . Map {
"Defaults" : config . getTemplateDefaults ( ) ,
"Themes" : dirs ,
"Config" : config ,
2023-10-31 22:59:09 +00:00
"Message" : message ,
2022-10-15 17:58:44 +00:00
} , "base" )
}
2023-05-01 16:33:15 +00:00
var (
errNoChanges = errors . New ( "no changes from previous config" )
errMissingField = errors . New ( "required field is missing" )
)
2022-10-15 17:58:44 +00:00
2023-05-01 16:33:15 +00:00
func ( config * Config ) saveAdminForm ( c * fiber . Ctx ) error {
2022-12-04 11:50:11 +00:00
requiredFields := [ ] string { "base_url" , "api_key" , "polled_smart_energy_summation" , "fossil_percentage" , "username" , "theme" , "name" , "installation_date" }
2022-10-15 17:58:44 +00:00
for _ , requiredField := range requiredFields {
if c . FormValue ( requiredField ) == "" {
2023-05-01 16:33:15 +00:00
return fmt . Errorf ( "%w: %s" , errMissingField , requiredField )
2022-10-15 17:58:44 +00:00
}
}
2022-12-04 11:50:11 +00:00
parsedTime , err := time . Parse ( "2006-01-02" , c . FormValue ( "installation_date" ) )
if err != nil {
return err
}
2023-10-29 17:32:35 +00:00
form := Config {
2023-05-01 17:25:16 +00:00
HomeAssistant : HomeAssistant { /*BaseURL to be filled later*/ APIKey : c . FormValue ( "api_key" ) , InstallationDate : dayStart ( parsedTime ) } ,
2022-10-15 17:58:44 +00:00
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*/ } ,
2023-10-31 22:59:09 +00:00
Dashboard : Dashboard { Theme : c . FormValue ( "theme" ) , Name : c . FormValue ( "name" ) , HeaderLinks : config . Dashboard . HeaderLinks , FooterLinks : config . Dashboard . FooterLinks /*MessageCard to be filled later*/ } ,
2022-10-15 17:58:44 +00:00
}
if c . FormValue ( "keep_old_password" ) == "" {
2023-05-01 17:39:12 +00:00
form . Administrator . PasswordHash = tools . Hash ( c . FormValue ( "password" ) )
2022-10-15 17:58:44 +00:00
} else {
form . Administrator . PasswordHash = config . Administrator . PasswordHash
}
2023-10-31 22:59:09 +00:00
if c . FormValue ( "motd_title" ) != "" || c . FormValue ( "motd_content" ) != "" {
2023-11-03 21:53:54 +00:00
// #nosec the input is admin-defined, and the admin is assumed to be trusted.
2023-10-31 22:59:09 +00:00
form . Dashboard . MOTD = & MessageCard {
Title : c . FormValue ( "motd_title" ) ,
Content : template . HTML ( c . FormValue ( "motd_content" ) ) ,
Style : c . FormValue ( "motd_style" ) ,
}
}
2022-10-15 17:58:44 +00:00
fmtURL , err := formatURL ( c . FormValue ( "base_url" ) )
if err != nil {
return err
}
form . HomeAssistant . BaseURL = fmtURL
2023-05-01 17:25:16 +00:00
if form . Equals ( config ) {
2023-05-01 16:33:15 +00:00
return errNoChanges
2022-10-15 17:58:44 +00:00
}
2023-01-04 14:57:16 +00:00
form . db = config . db
err = form . refreshCacheFromInstall ( )
2022-10-15 17:58:44 +00:00
if err != nil {
return err
}
js , err := json . Marshal ( form )
if err != nil {
return err
}
2023-10-29 17:32:35 +00:00
* config = form
2023-05-01 17:25:16 +00:00
return os . WriteFile ( "config.json" , js , 0 o600 )
2022-10-15 17:58:44 +00:00
}
2022-12-04 11:50:11 +00:00
func averageExcludingCurrentDay ( data [ ] float32 ) float32 {
2023-06-23 18:39:37 +00:00
if len ( data ) <= 1 {
2022-10-15 17:58:44 +00:00
return 0
}
data = data [ : len ( data ) - 1 ]
var sum float32
for _ , num := range data {
sum += num
}
2023-05-01 16:33:15 +00:00
avg := sum / float32 ( len ( data ) )
2022-10-15 17:58:44 +00:00
return float32 ( math . Floor ( float64 ( avg ) * 100 ) ) / 100
}
2023-05-01 17:39:12 +00:00
func ( config * Config ) RenderIndex ( c * fiber . Ctx ) error {
2022-12-04 11:50:11 +00:00
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" )
}
2023-01-29 20:16:04 +00:00
data , err := config . readHistory ( )
2022-10-15 17:58:44 +00:00
if err != nil {
return err
}
2023-05-01 16:33:15 +00:00
labels := make ( [ ] string , 0 , len ( data ) )
greenEnergyConsumptionAbsolute := make ( [ ] float32 , 0 , len ( data ) )
greenEnergyPercents := make ( [ ] float32 , 0 , len ( data ) )
energyConsumptions := make ( [ ] float32 , 0 , len ( data ) )
2022-10-15 17:58:44 +00:00
for _ , datum := range data {
2022-12-07 16:54:46 +00:00
labels = append ( labels , time . Unix ( datum . Date , 0 ) . Format ( "02/01" ) )
2022-10-15 17:58:44 +00:00
greenEnergyPercents = append ( greenEnergyPercents , datum . GreenEnergyPercentage )
greenEnergyConsumptionAbsolute = append ( greenEnergyConsumptionAbsolute , datum . GreenEnergyPercentage / 100 * datum . PolledSmartEnergySummation )
energyConsumptions = append ( energyConsumptions , datum . PolledSmartEnergySummation )
}
2022-12-04 11:50:11 +00:00
perDayUsage := averageExcludingCurrentDay ( energyConsumptions )
2022-10-15 17:58:44 +00:00
return c . Render ( "index" , fiber . Map {
"Defaults" : config . getTemplateDefaults ( ) ,
"Labels" : labels ,
"GreenEnergyPercents" : greenEnergyConsumptionAbsolute ,
"EnergyConsumptions" : energyConsumptions ,
2022-12-04 11:50:11 +00:00
"GreenEnergyPercent" : averageExcludingCurrentDay ( greenEnergyPercents ) ,
2022-10-15 17:58:44 +00:00
"PerDayUsage" : perDayUsage ,
2023-10-31 22:59:09 +00:00
"MOTD" : config . Dashboard . MOTD ,
2022-10-15 17:58:44 +00:00
} , "base" )
}