diff --git a/.gitignore b/.gitignore index 1ed37ab..7009ece 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ .vscode/ dist/ *.boltdb -tmp/ \ No newline at end of file +tmp/ +/vendor/ diff --git a/app/app.go b/app/app.go index 6bec9c4..f178672 100644 --- a/app/app.go +++ b/app/app.go @@ -1,24 +1,79 @@ package app +import ( + "github.com/typomedia/patchouli/app/store/boltdb" + "github.com/typomedia/patchouli/app/structs" +) + var app *Application type Application struct { - Name string - Version string - Author string - Description string - MailTemplate string + Name string + Version string + Author string + Description string + MailTemplate string + NotifyTemplate string + Config structs.Config } func new() *Application { app = &Application{} app.Name = "Patchouli" - app.Version = "0.2.0" + app.Version = "0.2.1" app.Author = "Philipp Speck " app.Description = "Patch Management Planner" + err := app.LoadConfig() + if err != nil { + panic(err) + } + if app.Config.Security.Generated { + err = app.StoreConfig() + if err != nil { + panic(err) + } + } return app } + +func (app *Application) StoreConfig() error { + db := boltdb.New() + defer db.Close() + + err := db.SetBucket("config") + if err != nil { + return err + } + + err = db.Set("main", app.Config, "config") + if err != nil { + return err + } + + return nil +} + +func (app *Application) LoadConfig() error { + db := boltdb.New() + defer db.Close() + + config, err := db.GetConfig() + if err != nil { + return err + } + + if !config.Security.Generated { + err = config.GenerateCipherKey() + if err != nil { + return err + } + } + + app.Config = config + return nil +} + func GetApp() *Application { if app == nil { app = new() diff --git a/app/encryption/encryption.go b/app/encryption/encryption.go new file mode 100644 index 0000000..69ff2b8 --- /dev/null +++ b/app/encryption/encryption.go @@ -0,0 +1,64 @@ +package encryption + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/hex" + "errors" + "fmt" + "github.com/typomedia/patchouli/app" + "io" +) + +func EncryptString(stringToEncrypt string) (string, error) { + key, err := app.GetApp().Config.GetCipherKey() + if err != nil { + return "", err + } + aesKey, _ := hex.DecodeString(key) + plaintext := []byte(stringToEncrypt) + block, err := aes.NewCipher(aesKey) + if err != nil { + return "", err + } + aesGCM, err := cipher.NewGCM(block) + if err != nil { + return "", err + } + + nonce := make([]byte, aesGCM.NonceSize()) + if _, err = io.ReadFull(rand.Reader, nonce); err != nil { + return "", err + } + + ciphertext := aesGCM.Seal(nonce, nonce, plaintext, nil) + return fmt.Sprintf("%x", ciphertext), nil +} + +func DecryptString(encString string) (string, error) { + key, err := app.GetApp().Config.GetCipherKey() + + aesKey, _ := hex.DecodeString(key) + enc, _ := hex.DecodeString(encString) + block, err := aes.NewCipher(aesKey) + if err != nil { + return "", err + } + + aesGCM, err := cipher.NewGCM(block) + if err != nil { + return "", err + } + nonceSize := aesGCM.NonceSize() + if len(enc) < nonceSize { + return "", errors.New("ciphertext too short") + } + nonce, ciphertext := enc[:nonceSize], enc[nonceSize:] + plaintext, err := aesGCM.Open(nil, nonce, ciphertext, nil) + if err != nil { + return "", err + } + + return string(plaintext), nil +} diff --git a/app/handler/config/edit.go b/app/handler/config/edit.go index 271c50a..39c035a 100644 --- a/app/handler/config/edit.go +++ b/app/handler/config/edit.go @@ -1,30 +1,16 @@ package config import ( + "fmt" "github.com/gofiber/fiber/v2" - "github.com/typomedia/patchouli/app/store/boltdb" - "github.com/typomedia/patchouli/app/structs" - "golang.org/x/crypto/bcrypt" + "github.com/typomedia/patchouli/app" ) func Edit(c *fiber.Ctx) error { - db := boltdb.New() - - err := db.SetBucket("config") - if err != nil { - return err - } - - var config structs.Config - db.Get("main", &config, "config") - if config.Smtp.Password != "" { - passwordHash, err := bcrypt.GenerateFromPassword([]byte(config.Smtp.Password), bcrypt.DefaultCost) - if err != nil { - return err - } - config.Smtp.Password = string(passwordHash) + config := app.GetApp().Config + if config.General.Hostname == "" { + config.General.Hostname = fmt.Sprintf("%s://%s", c.Protocol(), c.Hostname()) } - defer db.Close() return c.Render("app/views/config/edit", fiber.Map{ "Config": config, }) diff --git a/app/handler/config/save.go b/app/handler/config/save.go index 0994a77..a66f32a 100644 --- a/app/handler/config/save.go +++ b/app/handler/config/save.go @@ -3,6 +3,8 @@ package config import ( "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/log" + "github.com/typomedia/patchouli/app" + "github.com/typomedia/patchouli/app/encryption" "github.com/typomedia/patchouli/app/store/boltdb" "github.com/typomedia/patchouli/app/structs" ) @@ -20,8 +22,21 @@ func Save(c *fiber.Ctx) error { log.Error(err) } + // update config from body with security params from app + config.Security = app.GetApp().Config.Security + + _, err = encryption.DecryptString(config.Smtp.Password) + if err != nil { // config.Smtp.Password is not encrypted, encrypt it + config.Smtp.Password, err = encryption.EncryptString(config.Smtp.Password) + if err != nil { + log.Error(err) + } + } + db.Set("main", config, "config") + app.GetApp().Config = config + defer db.Close() return c.Redirect("/config") diff --git a/app/handler/dashboard/filter/operator.go b/app/handler/dashboard/filter/operator.go index 3eda48b..12e26f7 100644 --- a/app/handler/dashboard/filter/operator.go +++ b/app/handler/dashboard/filter/operator.go @@ -1,92 +1,29 @@ package filter import ( - "encoding/json" - "sort" - "time" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/typomedia/patchouli/app/helper" "github.com/typomedia/patchouli/app/store/boltdb" "github.com/typomedia/patchouli/app/structs" ) func Operator(c *fiber.Ctx) error { - id := c.Params("id") + operatorId := c.Params("id") db := boltdb.New() + defer db.Close() - err := db.SetBucket("machine") + machines, err := db.GetActiveMachines() if err != nil { return err } - machines, _ := db.GetAllByOperatorId(id, "machine") - - Machines := structs.Machines{} - for _, v := range machines { - machine := structs.Machine{} - err = json.Unmarshal(v, &machine) - if err != nil { - return err + var operatorMachines structs.Machines + for _, m := range machines { + if m.Operator.Id == operatorId { + operatorMachines = append(operatorMachines, m) } - - lastUpdate, _ := db.GetLastByName(machine.Id, "history") - - update := structs.Update{} - err = json.Unmarshal(lastUpdate, &update) - if err != nil { - log.Error(err) - } - - machine.Update = update - - Machines = append(Machines, machine) - - } - - // sort machines by oldest update first - sort.Sort(structs.ByDate(Machines)) - - err = db.SetBucket("config") - if err != nil { - return err } - var config structs.Config - db.Get("main", &config, "config") - - defer db.Close() - - interval := config.General.Interval - for i := range Machines { - currentDate := time.Now() - - if Machines[i].Update.Date == "" { - Machines[i].Update.Date = "0000-00-00" - Machines[i].Status = "danger" - continue - } - - Machines[i].Update.Date = helper.UnixToDateString(Machines[i].Update.Date) - - date, err := time.Parse("2006-01-02", Machines[i].Update.Date) - if err != nil { - log.Error(err) - } - - Machines[i].Days = int(currentDate.Sub(date).Hours() / 24) - if Machines[i].Days > interval { - Machines[i].Status = "danger" - } else if Machines[i].Days > interval/3 { - Machines[i].Status = "warning" - } else { - Machines[i].Status = "success" - } - Machines[i].Days = interval - int(currentDate.Sub(date).Hours()/24) - - } return c.Render("app/views/dashboard/list", fiber.Map{ - "Machines": Machines, + "Machines": operatorMachines, }) } diff --git a/app/handler/dashboard/list.go b/app/handler/dashboard/list.go index 173dbb6..4f5ed34 100644 --- a/app/handler/dashboard/list.go +++ b/app/handler/dashboard/list.go @@ -1,95 +1,20 @@ package dashboard import ( - "encoding/json" - "sort" - "time" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/typomedia/patchouli/app/helper" "github.com/typomedia/patchouli/app/store/boltdb" - "github.com/typomedia/patchouli/app/structs" ) func List(c *fiber.Ctx) error { db := boltdb.New() + defer db.Close() - err := db.SetBucket("machine") - if err != nil { - return err - } - - machines, _ := db.GetAll("machine") - - Machines := structs.Machines{} - for _, v := range machines { - machine := structs.Machine{} - err = json.Unmarshal(v, &machine) - if err != nil { - return err - } - - lastUpdate, _ := db.GetLastByName(machine.Id, "history") - - if lastUpdate != nil { - update := structs.Update{} - err = json.Unmarshal(lastUpdate, &update) - if err != nil { - log.Error(err) - } - machine.Update = update - } - - if !machine.Inactive { - Machines = append(Machines, machine) - } - - } - - // sort machines by oldest update first - sort.Sort(structs.ByDate(Machines)) - - err = db.SetBucket("config") + machines, err := db.GetActiveMachines() if err != nil { return err } - var config structs.Config - db.Get("main", &config, "config") - - defer db.Close() - - interval := config.General.Interval - for i := range Machines { - currentDate := time.Now() - - if Machines[i].Update.Date == "" { - Machines[i].Update.Date = "0000-00-00" - Machines[i].Status = "danger" - continue - } - - Machines[i].Update.Date = helper.UnixToDateString(Machines[i].Update.Date) - - date, err := time.Parse("2006-01-02", Machines[i].Update.Date) - if err != nil { - log.Error(err) - } - - Machines[i].Days = int(currentDate.Sub(date).Hours() / 24) - if Machines[i].Days > interval { - Machines[i].Status = "danger" - } else if Machines[i].Days > interval/3 { - Machines[i].Status = "warning" - } else { - Machines[i].Status = "success" - } - Machines[i].Days = interval - int(currentDate.Sub(date).Hours()/24) - - } - return c.Render("app/views/dashboard/list", fiber.Map{ - "Machines": Machines, + "Machines": machines, }) } diff --git a/app/handler/machine/edit.go b/app/handler/machine/edit.go index 1dd5680..b812cab 100644 --- a/app/handler/machine/edit.go +++ b/app/handler/machine/edit.go @@ -3,6 +3,7 @@ package machine import ( "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/log" + "github.com/typomedia/patchouli/app" "github.com/typomedia/patchouli/app/store/boltdb" "github.com/typomedia/patchouli/app/structs" ) @@ -23,6 +24,10 @@ func Edit(c *fiber.Ctx) error { log.Error(err) } + config := app.GetApp().Config + if machine.Interval == 0 { + machine.Interval = config.General.Interval + } defer db.Close() return c.Render("app/views/machine/edit", fiber.Map{ diff --git a/app/handler/machine/filter/operator.go b/app/handler/machine/filter/operator.go index d8d0293..34ca66b 100644 --- a/app/handler/machine/filter/operator.go +++ b/app/handler/machine/filter/operator.go @@ -1,40 +1,22 @@ package filter import ( - "encoding/json" - "github.com/gofiber/fiber/v2" "github.com/typomedia/patchouli/app/store/boltdb" - "github.com/typomedia/patchouli/app/structs" ) func Operator(c *fiber.Ctx) error { - id := c.Params("id") + operatorId := c.Params("id") db := boltdb.New() - // set bucket - err := db.SetBucket("machine") + machines, err := db.GetAllMachinesByOperatorId(operatorId) if err != nil { return err } - machines, _ := db.GetAllByOperatorId(id, "machine") - - Machines := structs.Machines{} - - for _, v := range machines { - machine := structs.Machine{} - err = json.Unmarshal(v, &machine) - if err != nil { - return err - } - Machines = append(Machines, machine) - - } - defer db.Close() return c.Render("app/views/machine/list", fiber.Map{ - "Machines": Machines, + "Machines": machines, }) } diff --git a/app/handler/machine/filter/system.go b/app/handler/machine/filter/system.go new file mode 100644 index 0000000..03b2417 --- /dev/null +++ b/app/handler/machine/filter/system.go @@ -0,0 +1,22 @@ +package filter + +import ( + "github.com/gofiber/fiber/v2" + "github.com/typomedia/patchouli/app/store/boltdb" +) + +func System(c *fiber.Ctx) error { + systemId := c.Params("id") + db := boltdb.New() + + Machines, err := db.GetAllMachinesBySystemId(systemId) + if err != nil { + return err + } + + defer db.Close() + + return c.Render("app/views/machine/list", fiber.Map{ + "Machines": Machines, + }) +} diff --git a/app/handler/machine/list.go b/app/handler/machine/list.go index a0f1812..621c9f5 100644 --- a/app/handler/machine/list.go +++ b/app/handler/machine/list.go @@ -1,8 +1,6 @@ package machine import ( - "encoding/json" - "github.com/gofiber/fiber/v2" "github.com/typomedia/patchouli/app/store/boltdb" "github.com/typomedia/patchouli/app/structs" @@ -11,32 +9,20 @@ import ( func List(c *fiber.Ctx) error { db := boltdb.New() - // set bucket - err := db.SetBucket("machine") - if err != nil { - return err - } - - machines, _ := db.GetAll("machine") + machines, _ := db.GetAllMachines(false) - Machines := structs.Machines{} - inactiveMachines := structs.Machines{} + var Machines, active, inactive structs.Machines - for _, v := range machines { - machine := structs.Machine{} - err = json.Unmarshal(v, &machine) - if err != nil { - return err - } + for _, machine := range machines { if machine.Inactive { - inactiveMachines = append(inactiveMachines, machine) + inactive = append(inactive, machine) } else { - Machines = append(Machines, machine) + active = append(active, machine) } } - Machines = append(Machines, inactiveMachines...) + Machines = append(active, inactive...) defer db.Close() diff --git a/app/handler/operator/activate.go b/app/handler/operator/activate.go new file mode 100644 index 0000000..7b8852f --- /dev/null +++ b/app/handler/operator/activate.go @@ -0,0 +1,35 @@ +package operator + +import ( + "github.com/gofiber/fiber/v2" + "github.com/typomedia/patchouli/app/store/boltdb" + "github.com/typomedia/patchouli/app/structs" +) + +func Activate(c *fiber.Ctx) error { + operatorId := c.Params("id") + + db := boltdb.New() + + err := db.SetBucket("operator") + if err != nil { + return err + } + defer db.Close() + + var operator structs.Operator + err = db.Get(operatorId, &operator, "operator") + if err != nil { + return err + } + + operator.Inactive = false + + err = db.Set(operatorId, operator, "operator") + + if err != nil { + return err + } + + return c.Redirect("/operator") +} diff --git a/app/handler/operator/deactivate.go b/app/handler/operator/deactivate.go new file mode 100644 index 0000000..32e2590 --- /dev/null +++ b/app/handler/operator/deactivate.go @@ -0,0 +1,34 @@ +package operator + +import ( + "github.com/gofiber/fiber/v2" + "github.com/typomedia/patchouli/app/store/boltdb" + "github.com/typomedia/patchouli/app/structs" +) + +func Deactivate(c *fiber.Ctx) error { + operatorId := c.Params("id") + + db := boltdb.New() + + err := db.SetBucket("operator") + if err != nil { + return err + } + defer db.Close() + + var operator structs.Operator + err = db.Get(operatorId, &operator, "operator") + if err != nil { + return err + } + + operator.Inactive = true + err = db.Set(operatorId, operator, "operator") + + if err != nil { + return err + } + + return c.Redirect("/operator") +} diff --git a/app/handler/operator/list.go b/app/handler/operator/list.go index 7cfe4e1..e1c4cb5 100644 --- a/app/handler/operator/list.go +++ b/app/handler/operator/list.go @@ -1,36 +1,21 @@ package operator import ( - "encoding/json" "github.com/gofiber/fiber/v2" "github.com/typomedia/patchouli/app/store/boltdb" - "github.com/typomedia/patchouli/app/structs" ) func List(c *fiber.Ctx) error { db := boltdb.New() - err := db.SetBucket("operator") + operators, err := db.GetAllOperators() if err != nil { return err } - operators, _ := db.GetAll("operator") - - Operators := structs.Operators{} - for _, v := range operators { - operator := structs.Operator{} - err = json.Unmarshal(v, &operator) - if err != nil { - return err - } - Operators = append(Operators, operator) - - } - defer db.Close() return c.Render("app/views/operator/list", fiber.Map{ - "Operators": Operators, + "Operators": operators, }) } diff --git a/app/handler/system/list.go b/app/handler/system/list.go index 4690475..7a98171 100644 --- a/app/handler/system/list.go +++ b/app/handler/system/list.go @@ -1,10 +1,12 @@ package system import ( + "cmp" "encoding/json" "github.com/gofiber/fiber/v2" "github.com/typomedia/patchouli/app/store/boltdb" "github.com/typomedia/patchouli/app/structs" + "slices" ) func List(c *fiber.Ctx) error { @@ -24,9 +26,16 @@ func List(c *fiber.Ctx) error { if err != nil { return err } + machinesOfSystem, err := db.GetMachinesBySystem(system.Id) + if err != nil { + return err + } + system.MachineCount = len(machinesOfSystem) Systems = append(Systems, system) } - + slices.SortFunc(Systems, func(a, b structs.System) int { + return cmp.Compare(b.MachineCount, a.MachineCount) + }) defer db.Close() return c.Render("app/views/system/list", fiber.Map{ diff --git a/app/handler/update/list.go b/app/handler/update/list.go index 4203ed5..4ed107c 100644 --- a/app/handler/update/list.go +++ b/app/handler/update/list.go @@ -22,7 +22,7 @@ func List(c *fiber.Ctx) error { log.Error(err) } - days, _ := db.GetAllByName(id, "history") + days, _ := db.GetAllUpdatesByMachineId(id) Updates := structs.Updates{} diff --git a/app/handler/update/mail.go b/app/handler/update/mail.go index 83cbec9..7c816b0 100644 --- a/app/handler/update/mail.go +++ b/app/handler/update/mail.go @@ -7,6 +7,7 @@ import ( "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/log" patchouli "github.com/typomedia/patchouli/app" + "github.com/typomedia/patchouli/app/encryption" "github.com/typomedia/patchouli/app/store/boltdb" "github.com/typomedia/patchouli/app/structs" ) @@ -36,15 +37,19 @@ func Mail(c *fiber.Ctx) error { log.Error(err) } - var config structs.Config - err = db.Get("main", &config, "config") + config, err := db.GetConfig() + if err != nil { + log.Error(err) + } + + smtpPasswd, err := encryption.DecryptString(config.Smtp.Password) if err != nil { log.Error(err) } opts := tinymail.MailerOpts{ User: config.Smtp.Username, - Password: config.Smtp.Password, + Password: smtpPasswd, Host: config.Smtp.Host, Port: config.Smtp.Port, } diff --git a/app/notifier/notifier.go b/app/notifier/notifier.go new file mode 100644 index 0000000..74c9a60 --- /dev/null +++ b/app/notifier/notifier.go @@ -0,0 +1,84 @@ +package notifier + +import ( + "github.com/XotoX1337/tinymail" + "github.com/gofiber/fiber/v2/log" + "github.com/typomedia/patchouli/app" + + "github.com/typomedia/patchouli/app/encryption" + "github.com/typomedia/patchouli/app/store/boltdb" + "github.com/typomedia/patchouli/app/structs" +) + +const NOTIFY_DAYS = 7 + +type Notifier struct { + Operators map[string]structs.Machines +} + +func (n Notifier) Run() { + db := boltdb.New() + defer db.Close() + + config := app.GetApp().Config + + machines, err := db.GetActiveMachines() + if err != nil { + log.Fatal(err) + } + n.Operators = make(map[string]structs.Machines) + for _, machine := range machines { + if machine.Days > NOTIFY_DAYS { + continue + } + n.Operators[machine.Operator.Id] = append(n.Operators[machine.Operator.Id], machine) + } + + smtpPasswd, err := encryption.DecryptString(config.Smtp.Password) + if err != nil { + return + } + opts := tinymail.MailerOpts{ + User: config.Smtp.Username, + Password: smtpPasswd, + Host: config.Smtp.Host, + Port: config.Smtp.Port, + } + + mailer, err := tinymail.New(opts) + if err != nil { + log.Fatal(err) + } + + for operatorId, machines := range n.Operators { + + operator, err := db.GetOperatorById(operatorId) + if err != nil { + log.Fatal(err) + } + + tplData := map[string]interface{}{ + "Machines": machines, + "Operator": operator, + "Hostname": config.General.Hostname, + "Version": app.GetApp().Version, + "App": app.GetApp().Name, + } + + msg, err := tinymail.FromTemplateString(tplData, app.GetApp().NotifyTemplate) + if err != nil { + log.Error(err) + } + + msg.SetFrom(config.Smtp.Sender) + msg.SetTo(config.General.Email) + msg.SetSubject("Patchmgmt: Maschinen benötigen Updates!") + + mailer.SetMessage(msg) + + err = mailer.SetMessage(msg).Send() + if err != nil { + log.Fatal(err) + } + } +} diff --git a/app/store/boltdb/boltdb.go b/app/store/boltdb/boltdb.go index 2d2b4b5..38c2903 100644 --- a/app/store/boltdb/boltdb.go +++ b/app/store/boltdb/boltdb.go @@ -2,6 +2,8 @@ package boltdb import ( "encoding/json" + "github.com/typomedia/patchouli/app/helper" + "sort" "time" "github.com/typomedia/patchouli/app/structs" @@ -68,6 +70,21 @@ func (bolt *Bolt) Get(key string, result interface{}, bucket string) error { return err } +func (bolt *Bolt) GetConfig() (structs.Config, error) { + var data []byte + var result structs.Config + err := bolt.db.View(func(tx *bbolt.Tx) error { + bucket := tx.Bucket([]byte("config")) + data = bucket.Get([]byte("main")) + err := json.Unmarshal(data, &result) + if err != nil { + return err + } + return nil + }) + return result, err +} + func (bolt *Bolt) GetLastByName(id string, bucket string) ([]byte, error) { var result []byte err := bolt.db.View(func(tx *bbolt.Tx) error { @@ -127,10 +144,10 @@ func (bolt *Bolt) GetAll(bucket string) ([][]byte, error) { return result, err } -func (bolt *Bolt) GetAllByName(id string, bucket string) ([][]byte, error) { +func (bolt *Bolt) GetAllUpdatesByMachineId(id string) ([][]byte, error) { var result [][]byte err := bolt.db.View(func(tx *bbolt.Tx) error { - bucket := tx.Bucket([]byte(bucket)) + bucket := tx.Bucket([]byte("history")) cursor := bucket.Cursor() for k, v := cursor.First(); k != nil; k, v = cursor.Next() { var update structs.Update @@ -147,24 +164,173 @@ func (bolt *Bolt) GetAllByName(id string, bucket string) ([][]byte, error) { return result, err } -func (bolt *Bolt) GetAllByOperatorId(id string, bucket string) ([][]byte, error) { - var result [][]byte - err := bolt.db.View(func(tx *bbolt.Tx) error { - bucket := tx.Bucket([]byte(bucket)) - cursor := bucket.Cursor() - for k, v := cursor.First(); k != nil; k, v = cursor.Next() { - var machine structs.Machine - err := json.Unmarshal(v, &machine) +func (bolt *Bolt) GetAllMachinesByOperatorId(id string) (structs.Machines, error) { + var result structs.Machines + machines, err := bolt.GetAllMachines(false) + if err != nil { + return nil, err + } + for _, m := range machines { + if m.Operator.Id != id { + continue + } + result = append(result, m) + } + return result, nil +} + +func (bolt *Bolt) GetAllMachinesBySystemId(systemId string) (structs.Machines, error) { + var result structs.Machines + machines, err := bolt.GetAllMachines(false) + + for _, m := range machines { + if m.System.Id != systemId { + continue + } + result = append(result, m) + } + return result, err +} + +func (bolt *Bolt) GetAllMachines(onlyActive bool) (structs.Machines, error) { + var result structs.Machines + config, err := bolt.GetConfig() + machines, err := bolt.GetAll("machine") + if err != nil { + return nil, err + } + for _, v := range machines { + m := structs.Machine{} + err = json.Unmarshal(v, &m) + if err != nil { + return nil, err + } + if m.Inactive && onlyActive { + continue + } + if m.Interval == 0 { + m.Interval = config.General.Interval + } + result = append(result, m) + } + + return result, nil +} + +func (bolt *Bolt) GetActiveMachines() (structs.Machines, error) { + Machines, err := bolt.GetAllMachines(true) + if err != nil { + return nil, err + } + + config, err := bolt.GetConfig() + if err != nil { + return nil, err + } + + for i := range Machines { + currentDate := time.Now() + + lastUpdate, _ := bolt.GetLastByName(Machines[i].Id, "history") + + if lastUpdate != nil { + update := structs.Update{} + err = json.Unmarshal(lastUpdate, &update) if err != nil { - return err - } - if machine.Operator.Id == id { - result = append(result, v) + return nil, err } + Machines[i].Update = update } - return nil + + if Machines[i].Update.Date == "" { + Machines[i].Update.Date = "0000-00-00" + Machines[i].Status = "danger" + continue + } + + Machines[i].Update.Date = helper.UnixToDateString(Machines[i].Update.Date) + + date, err := time.Parse(time.DateOnly, Machines[i].Update.Date) + if err != nil { + return nil, err + } + + var interval int + // check if machine has custom interval + if Machines[i].Interval != 0 { + interval = Machines[i].Interval + } else { + interval = config.General.Interval + } + + Machines[i].Days = int(currentDate.Sub(date).Hours() / 24) + if Machines[i].Days > interval { + Machines[i].Status = "danger" + } else if Machines[i].Days > interval/3 { + Machines[i].Status = "warning" + } else { + Machines[i].Status = "success" + } + Machines[i].Days = interval - int(currentDate.Sub(date).Hours()/24) + + } + + // sort machines by oldest update first + sort.Slice(Machines, func(i, j int) bool { + return Machines[i].Update.Date < Machines[j].Update.Date }) - return result, err + + return Machines, nil +} + +func (bolt *Bolt) GetAllOperators() (structs.Operators, error) { + operators, _ := bolt.GetAll("operator") + + Operators := structs.Operators{} + InactiveOperators := structs.Operators{} + for _, v := range operators { + operator := structs.Operator{} + err := json.Unmarshal(v, &operator) + if err != nil { + return nil, err + } + + if operator.Inactive { + InactiveOperators = append(InactiveOperators, operator) + } else { + Operators = append(Operators, operator) + } + } + return append(Operators, InactiveOperators...), nil +} + +func (bolt *Bolt) GetOperatorById(id string) (structs.Operator, error) { + var operator structs.Operator + err := bolt.Get(id, &operator, "operator") + if err != nil { + return operator, err + } + return operator, nil +} + +func (bolt *Bolt) GetMachinesBySystem(systemId string) (structs.Machines, error) { + machines, err := bolt.GetAll("machine") + if err != nil { + return nil, err + } + machinesOfSystem := structs.Machines{} + for _, v := range machines { + machine := structs.Machine{} + err := json.Unmarshal(v, &machine) + if err != nil { + return nil, err + } + if machine.System.Id == systemId { + machinesOfSystem = append(machinesOfSystem, machine) + } + } + + return machinesOfSystem, nil } func (bolt *Bolt) Close() error { diff --git a/app/structs/config.go b/app/structs/config.go index b8b781a..6e692a7 100644 --- a/app/structs/config.go +++ b/app/structs/config.go @@ -1,14 +1,21 @@ package structs +import ( + "crypto/rand" + "encoding/hex" +) + type Config struct { - General General `json:"general"` - Smtp Smtp `json:"smtp"` + General General `json:"general"` + Smtp Smtp `json:"smtp"` + Security Security `json:"security"` } type General struct { Company string `json:"company"` Email string `json:"email"` Interval int `json:"interval"` + Hostname string `json:"hostname"` } type Smtp struct { @@ -18,3 +25,30 @@ type Smtp struct { Username string `json:"username"` Password string `json:"password"` } + +type Security struct { + CipherKey string `json:"cipher_key"` + Generated bool `json:"generated"` +} + +func (c *Config) GenerateCipherKey() error { + bytes := make([]byte, 32) + if _, err := rand.Read(bytes); err != nil { + return err + } + c.Security.CipherKey = hex.EncodeToString(bytes) + c.Security.Generated = true + + return nil +} + +func (c *Config) GetCipherKey() (string, error) { + if c.Security.CipherKey != "" { + return c.Security.CipherKey, nil + } + err := c.GenerateCipherKey() + if err != nil { + return "", err + } + return c.Security.CipherKey, nil +} diff --git a/app/structs/machines.go b/app/structs/machines.go index 1633806..63d2b7a 100644 --- a/app/structs/machines.go +++ b/app/structs/machines.go @@ -30,4 +30,5 @@ type Machine struct { Days int `json:"days"` // remaining days Status string `json:"status"` // status color Inactive bool `json:"inactive"` + Interval int `json:"interval"` } diff --git a/app/structs/operators.go b/app/structs/operators.go index ceb9a93..35f5190 100644 --- a/app/structs/operators.go +++ b/app/structs/operators.go @@ -1,5 +1,7 @@ package structs +import "strings" + type Operators []Operator type Operator struct { @@ -7,4 +9,13 @@ type Operator struct { Name string `json:"operator"` Department string `json:"department"` Email string `json:"email"` + Inactive bool `json:"inactive"` +} + +func (o Operator) Firstname() string { + return strings.Split(o.Name, " ")[0] +} + +func (o Operator) Lastname() string { + return strings.Split(o.Name, " ")[1] } diff --git a/app/structs/systems.go b/app/structs/systems.go index db9c537..8d7332b 100644 --- a/app/structs/systems.go +++ b/app/structs/systems.go @@ -1,10 +1,21 @@ package structs +import "time" + type Systems []System type System struct { - Id string - Name string - LTS bool - EOL string + Id string + Name string + LTS bool + EOL string + MachineCount int +} + +func (s System) IsEOL() bool { + eolTime, err := time.Parse(time.DateOnly, s.EOL) + if err != nil { + return false + } + return eolTime.UTC().Before(time.Now().UTC()) } diff --git a/app/views/config/edit.html b/app/views/config/edit.html index b1f5981..ce2e3c0 100644 --- a/app/views/config/edit.html +++ b/app/views/config/edit.html @@ -17,6 +17,10 @@

General

+
+ + +

Mailserver

diff --git a/app/views/machine/edit.html b/app/views/machine/edit.html index 9c3d336..fd4f95d 100644 --- a/app/views/machine/edit.html +++ b/app/views/machine/edit.html @@ -2,11 +2,16 @@
+
+
+ + +
- - {{ else }} -
- -
- {{ end }}
{{ if .Machine.Inactive }} + {{ else }} + {{ end }} diff --git a/app/views/machine/list.html b/app/views/machine/list.html index 4a66752..c6f7ddb 100644 --- a/app/views/machine/list.html +++ b/app/views/machine/list.html @@ -3,6 +3,7 @@ New + Interval System Address Domain @@ -16,7 +17,16 @@ {{range .Machines}} {{ .Name }} - {{ .System.Name }} + {{ .Interval }} + + {{ if .System.IsEOL }} + + + + {{ end }} + {{ .System.Name }} + {{ .Ip }} {{ .Fqdn }} {{if .Operator.Email}} diff --git a/app/views/operator/edit.html b/app/views/operator/edit.html index c8fad85..8aed312 100644 --- a/app/views/operator/edit.html +++ b/app/views/operator/edit.html @@ -2,6 +2,7 @@
+
@@ -14,9 +15,21 @@
+
-
+
+ {{ if .Operator.Inactive }} + + + {{ else }} + + + {{ end }}
diff --git a/app/views/operator/list.html b/app/views/operator/list.html index 09d3fc9..b22c8dd 100644 --- a/app/views/operator/list.html +++ b/app/views/operator/list.html @@ -5,6 +5,7 @@ New Department Email + Status @@ -13,6 +14,13 @@ {{.Name}} {{.Department}} {{ .Email }} + + {{ if .Inactive }} + inactive + {{ else }} + active + {{ end }} + {{end}} diff --git a/app/views/system/edit.html b/app/views/system/edit.html index 30c0cd4..bf4b1fe 100644 --- a/app/views/system/edit.html +++ b/app/views/system/edit.html @@ -14,6 +14,7 @@ {{ else }} {{ end }} + LTS
diff --git a/app/views/system/list.html b/app/views/system/list.html index 0fbc99c..5f71775 100644 --- a/app/views/system/list.html +++ b/app/views/system/list.html @@ -1,8 +1,9 @@ {{ template "header" . }} - +
+ @@ -11,6 +12,7 @@ {{range .Systems}} +
NewMachines LTS EOL
{{.Name}}{{ .MachineCount }} {{ if .LTS }} True @@ -23,4 +25,7 @@ {{end}}
+ {{ template "footer" . }} diff --git a/go.mod b/go.mod index f7822b8..0f54c6b 100644 --- a/go.mod +++ b/go.mod @@ -1,27 +1,27 @@ module github.com/typomedia/patchouli -go 1.20 +go 1.24.1 require ( github.com/XotoX1337/tinymail v0.7.0 - github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a - github.com/gofiber/fiber/v2 v2.52.0 - github.com/gofiber/template/html/v2 v2.0.5 - github.com/gorilla/schema v1.2.1 + github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 + github.com/gofiber/fiber/v2 v2.52.6 + github.com/gofiber/template/html/v2 v2.1.3 + github.com/gorilla/schema v1.4.1 github.com/oklog/ulid/v2 v2.1.0 - go.etcd.io/bbolt v1.3.8 - golang.org/x/crypto v0.32.0 + github.com/roylee0704/gron v0.0.0-20160621042432-e78485adab46 + go.etcd.io/bbolt v1.4.0 ) require ( - github.com/andybalholm/brotli v1.0.5 // indirect - github.com/gofiber/template v1.8.2 // indirect + github.com/andybalholm/brotli v1.1.0 // indirect + github.com/gofiber/template v1.8.3 // indirect github.com/gofiber/utils v1.1.0 // indirect - github.com/google/uuid v1.5.0 // indirect - github.com/klauspost/compress v1.17.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/klauspost/compress v1.17.9 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.51.0 // indirect diff --git a/go.sum b/go.sum index f976efc..2cfe0c4 100644 --- a/go.sum +++ b/go.sum @@ -1,50 +1,56 @@ github.com/XotoX1337/tinymail v0.7.0 h1:KvIaWvUSorZTh+DiY/iJfH5P7hjLen2AaWnelIJfY24= github.com/XotoX1337/tinymail v0.7.0/go.mod h1:eQ8xKUuxppTrCGPkBAtROv20OK1eRw9rsVto9yuwgPY= -github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= -github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a h1:RYfmiM0zluBJOiPDJseKLEN4BapJ42uSi9SZBQ2YyiA= -github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI= -github.com/gofiber/fiber/v2 v2.52.0 h1:S+qXi7y+/Pgvqq4DrSmREGiFwtB7Bu6+QFLuIHYw/UE= -github.com/gofiber/fiber/v2 v2.52.0/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= -github.com/gofiber/template v1.8.2 h1:PIv9s/7Uq6m+Fm2MDNd20pAFFKt5wWs7ZBd8iV9pWwk= -github.com/gofiber/template v1.8.2/go.mod h1:bs/2n0pSNPOkRa5VJ8zTIvedcI/lEYxzV3+YPXdBvq8= -github.com/gofiber/template/html/v2 v2.0.5 h1:BKLJ6Qr940NjntbGmpO3zVa4nFNGDCi/IfUiDB9OC20= -github.com/gofiber/template/html/v2 v2.0.5/go.mod h1:RCF14eLeQDCSUPp0IGc2wbSSDv6yt+V54XB/+Unz+LM= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 h1:FWNFq4fM1wPfcK40yHE5UO3RUdSNPaBC+j3PokzA6OQ= +github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI= +github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI= +github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= +github.com/gofiber/template v1.8.3 h1:hzHdvMwMo/T2kouz2pPCA0zGiLCeMnoGsQZBTSYgZxc= +github.com/gofiber/template v1.8.3/go.mod h1:bs/2n0pSNPOkRa5VJ8zTIvedcI/lEYxzV3+YPXdBvq8= +github.com/gofiber/template/html/v2 v2.1.3 h1:n1LYBtmr9C0V/k/3qBblXyMxV5B0o/gpb6dFLp8ea+o= +github.com/gofiber/template/html/v2 v2.1.3/go.mod h1:U5Fxgc5KpyujU9OqKzy6Kn6Qup6Tm7zdsISR+VpnHRE= github.com/gofiber/utils v1.1.0 h1:vdEBpn7AzIUJRhe+CiTOJdUcTg4Q9RK+pEa0KPbLdrM= github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0= -github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= -github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/schema v1.2.1 h1:tjDxcmdb+siIqkTNoV+qRH2mjYdr2hHe5MKXbp61ziM= -github.com/gorilla/schema v1.2.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM= -github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= -github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E= +github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU= github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/roylee0704/gron v0.0.0-20160621042432-e78485adab46 h1:dp1iW1JOTY63249ZTwxzwN0EKG6EvuPdfMohKo4EomY= +github.com/roylee0704/gron v0.0.0-20160621042432-e78485adab46/go.mod h1:MDhl6ujYU3dbEiGclVk5uA4pHEjTS659POKAtiAWB94= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= -go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA= -go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk= +go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 258a9f1..e4a83a1 100644 --- a/main.go +++ b/main.go @@ -3,13 +3,12 @@ package main import ( "embed" "fmt" - "log" - "net/http" - "time" - "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/log" "github.com/gofiber/fiber/v2/middleware/filesystem" "github.com/gofiber/template/html/v2" + "github.com/roylee0704/gron" + "github.com/roylee0704/gron/xtime" patchouli "github.com/typomedia/patchouli/app" "github.com/typomedia/patchouli/app/handler/api/csv" "github.com/typomedia/patchouli/app/handler/api/htmx" @@ -22,6 +21,9 @@ import ( "github.com/typomedia/patchouli/app/handler/operator" "github.com/typomedia/patchouli/app/handler/system" "github.com/typomedia/patchouli/app/handler/update" + "github.com/typomedia/patchouli/app/notifier" + "net/http" + "time" ) //go:embed app/views @@ -33,9 +35,13 @@ var public embed.FS //go:embed public/html/mail/update.html var mailTemplate string +//go:embed public/html/mail/notify.html +var notifyTemplate string + func main() { App := patchouli.GetApp() App.MailTemplate = mailTemplate + App.NotifyTemplate = notifyTemplate engine := html.NewFileSystem(http.FS(views), ".html") engine.AddFunc("Name", func() string { return App.Name @@ -59,6 +65,7 @@ func main() { app.Get("/machine/new", machine.New) app.Get("/machine/edit/:id", machine.Edit) app.Get("/machine/filter/operator/:id", machineFilter.Operator) + app.Get("/machine/filter/system/:id", machineFilter.System) app.Post("/machine/save/:id", machine.Save) app.Get("/machine/update/list/:id", update.List) @@ -75,6 +82,9 @@ func main() { app.Get("/operator/edit/:id", operator.Edit) app.Post("/operator/save/:id", operator.Save) + app.Get("/operator/deactivate/:id", operator.Deactivate) + app.Get("/operator/activate/:id", operator.Activate) + app.Get("/system", system.List) app.Get("/system/new", system.New) app.Get("/system/edit/:id", system.Edit) @@ -95,5 +105,17 @@ func main() { PathPrefix: "public", })) + app.Hooks().OnListen(func(listenData fiber.ListenData) error { + if fiber.IsChild() { + return nil + } + + n := notifier.Notifier{} + g := gron.New() + g.Add(gron.Every(1*xtime.Week), n) + g.Start() + return nil + }) + log.Fatal(app.Listen(":5000")) } diff --git a/public/css/styles.css b/public/css/styles.css index ea6fb7e..91f9d66 100644 --- a/public/css/styles.css +++ b/public/css/styles.css @@ -72,10 +72,13 @@ a:hover { background: #ffcccc; } .success { - background: #ccfecc; + background: #f8f9c7; } .warning { - background: #f8f9c7; + background: #ccfecc; +} +i.ri-error-warning-line { + color: #D93526; } i.mail-success { color: #23af23; diff --git a/public/css/styles.css.map b/public/css/styles.css.map index 1de1c61..93ce96c 100644 --- a/public/css/styles.css.map +++ b/public/css/styles.css.map @@ -1 +1 @@ -{"version":3,"sources":["styles.less"],"names":[],"mappings":";;;;AAQA;EACE,aAJY,qBAIZ;EACA,iBAAA;;AAFF,IAGE;EACE,sBAAA;EACA,mBAAA;EACA,kCAAA;;AANJ,IAGE,IAIE;EACE,cAAA;;AARN,IAGE,IAOE;EACE,aAAa,8BAAb;EACA,iBAAA;EACA,yBAAA;EACA,eAAA;;AAdN,IAiBE;EACE,aAAA;EACA,sBAAA;EACA,UAAA;;AApBJ,IAiBE,KAIE;EACE,eAAA;;AAKI,IAVR,KAOE,MAAK,KACH,MACE,GACG;EACC,SAAS,OAAT;EACA,aAAa,WAAb;EACA,gBAAA;EACA,mBAAA;EACA,WAAA;;AAhCZ,IAiBE,KAOE,MAAK,KAYH,MACE,GAAE,UAAU;EACV,mBAAA;;AAtCV,IAiBE,KAOE,MAAK,KAYH,MAIE,GAAE;EACA,mBAAA;;AAzCV,IA8CE;EACE,UAAA;EACA,kBAAA;;AAhDJ,IA8CE,OAGE;EACE,gBAAA;;AAlDN,IA8CE,OAME;EACE,qBAAA;EACA,WAAA;EACA,qBAAA;;AAKN;AAAI;AAAI;AAAI;AAAI;AAAI;EAClB,aAhEY,qBAgEZ;;AAGF;EACE,iBAAA;EACA,eAAA;;AAGF,CAAC;EACC,qBAAA;;AAGF;EACE,mBAAA;;AAGF;EACE,mBAAA;;AAGF;EACE,mBAAA;;AAGF,CAAC;EACC,cAAA;EACA,eAAA;;AAEF,CAAC;EACC,cAAA;EACA,eAAA;;AAGF;EACE,mBAAA;;AAGF;EACE,mBAAA;;AAGF,cAAc,IAAI;EAChB,eAAe,0BAA0B,yBAAzC;EACA,qBAAA;EACA,YAAA","file":"styles.css"} \ No newline at end of file +{"version":3,"sources":["styles.less"],"names":[],"mappings":";;;;AAWA;EACE,aAPY,qBAOZ;EACA,iBAAA;;AAFF,IAGE;EACE,sBAAA;EACA,mBAAA;EACA,kCAAA;;AANJ,IAGE,IAIE;EACE,cAAA;;AARN,IAGE,IAOE;EACE,aAAa,8BAAb;EACA,iBAAA;EACA,yBAAA;EACA,eAAA;;AAdN,IAiBE;EACE,aAAA;EACA,sBAAA;EACA,UAAA;;AApBJ,IAiBE,KAIE;EACE,eAAA;;AAKI,IAVR,KAOE,MAAK,KACH,MACE,GACG;EACC,SAAS,OAAT;EACA,aAAa,WAAb;EACA,gBAAA;EACA,mBAAA;EACA,WAAA;;AAhCZ,IAiBE,KAOE,MAAK,KAYH,MACE,GAAE,UAAU;EACV,mBAAA;;AAtCV,IAiBE,KAOE,MAAK,KAYH,MAIE,GAAE;EACA,mBAAA;;AAzCV,IA8CE;EACE,UAAA;EACA,kBAAA;;AAhDJ,IA8CE,OAGE;EACE,gBAAA;;AAlDN,IA8CE,OAME;EACE,qBAAA;EACA,WAAA;EACA,qBAAA;;AAKN;AAAI;AAAI;AAAI;AAAI;AAAI;EAClB,aAnEY,qBAmEZ;;AAGF;EACE,iBAAA;EACA,eAAA;;AAGF,CAAC;EACC,qBAAA;;AAGF;EACE,mBAAA;;AAGF;EACE,mBAAA;;AAGF;EACE,mBAAA;;AAGF,CAAC;EACG,cAAA;;AAGJ,CAAC;EACC,cAAA;EACA,eAAA;;AAEF,CAAC;EACC,cAAA;EACA,eAAA;;AAGF;EACE,mBAAA;;AAGF;EACE,mBAAA;;AAGF,cAAc,IAAI;EAChB,eAAe,0BAA0B,yBAAzC;EACA,qBAAA;EACA,YAAA","file":"styles.css"} \ No newline at end of file diff --git a/public/css/styles.less b/public/css/styles.less index 358e39f..415dec9 100644 --- a/public/css/styles.less +++ b/public/css/styles.less @@ -5,6 +5,9 @@ @font-family: 'Signika', sans-serif; @color-primary: rgb(65, 84, 98); +@color-danger: #ffcccc; +@color-warning: #ccfecc; +@color-success: #f8f9c7; body { font-family: @font-family; @@ -80,15 +83,19 @@ a:hover { } .danger { - background: #ffcccc; + background: @color-danger; } .success { - background: #ccfecc; + background: @color-success; } .warning { - background: #f8f9c7; + background: @color-warning; +} + +i.ri-error-warning-line { + color: #D93526; } i.mail-success { diff --git a/public/html/mail/notify.html b/public/html/mail/notify.html new file mode 100644 index 0000000..d22c7ce --- /dev/null +++ b/public/html/mail/notify.html @@ -0,0 +1,451 @@ + + + + + + Patchouli - Stay secure + + + + {{.Operator.Firstname}}, deine Server brauchen Dich! + + + + + + + + + \ No newline at end of file diff --git a/public/img/favicon.svg b/public/img/favicon.svg index b51809d..5d62ac4 100644 --- a/public/img/favicon.svg +++ b/public/img/favicon.svg @@ -1 +1,4 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/img/patchouli.svg b/public/img/patchouli.svg index b4bcfc6..0609200 100644 --- a/public/img/patchouli.svg +++ b/public/img/patchouli.svg @@ -1 +1,3 @@ - \ No newline at end of file + \ No newline at end of file