Initial commit
This commit is contained in:
commit
89f483171a
43 changed files with 2245 additions and 0 deletions
391
main.go
Normal file
391
main.go
Normal file
|
|
@ -0,0 +1,391 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
// External
|
||||
ws "git.gibonuddevalla.se/go/webservice"
|
||||
"git.gibonuddevalla.se/go/webservice/session"
|
||||
we "git.gibonuddevalla.se/go/wrappederror"
|
||||
|
||||
// Standard
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/fs"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const VERSION = "v1"
|
||||
|
||||
var (
|
||||
logger *slog.Logger
|
||||
flagConfigFile string
|
||||
flagDev bool
|
||||
service *ws.Service
|
||||
logFile *os.File
|
||||
parsedTemplates map[string]*template.Template
|
||||
componentFilenames []string
|
||||
|
||||
//go:embed sql
|
||||
sqlFS embed.FS
|
||||
|
||||
//go:embed views
|
||||
viewFS embed.FS
|
||||
|
||||
//go:embed static
|
||||
staticFS embed.FS
|
||||
)
|
||||
|
||||
func init() { // {{{
|
||||
opts := slog.HandlerOptions{}
|
||||
logger = slog.New(slog.NewJSONHandler(os.Stdout, &opts))
|
||||
|
||||
confDir, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
logger.Error("application", "error", we.Wrap(err))
|
||||
}
|
||||
cfgPath := path.Join(confDir, "smon.yaml")
|
||||
flag.StringVar(&flagConfigFile, "config", cfgPath, "Path and filename of the YAML configuration file")
|
||||
flag.BoolVar(&flagDev, "dev", false, "reload templates on each request")
|
||||
flag.Parse()
|
||||
|
||||
componentFilenames, err = getComponentFilenames(viewFS)
|
||||
if err != nil {
|
||||
logger.Error("application", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
parsedTemplates = make(map[string]*template.Template)
|
||||
} // }}}
|
||||
|
||||
func main() { // {{{
|
||||
var err error
|
||||
|
||||
we.Init()
|
||||
we.SetLogCallback(logHandler)
|
||||
|
||||
service, err = ws.New(flagConfigFile, VERSION, logger)
|
||||
if err != nil {
|
||||
logger.Error("application", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
smonConf := SmonConfiguration{}
|
||||
j, _ := json.Marshal(service.Config.Application)
|
||||
json.Unmarshal(j, &smonConf)
|
||||
logFile, err = os.OpenFile(smonConf.LogFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
|
||||
if err != nil {
|
||||
logger.Error("application", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.SetDatabase(sqlProvider)
|
||||
service.SetStaticFS(staticFS, "static")
|
||||
service.SetStaticDirectory("static", flagDev)
|
||||
service.Register("/", false, false, staticHandler)
|
||||
service.Register("/problems", false, false, pageProblems)
|
||||
service.Register("/triggers", false, false, pageTriggers)
|
||||
service.Register("/trigger/edit/{id}", false, false, pageTriggerEdit)
|
||||
service.Register("/trigger/update/{id}", false, false, pageTriggerUpdate)
|
||||
service.Register("/trigger/run/{id}", false, false, pageTriggerRun)
|
||||
service.Register("/configuration", false, false, pageConfiguration)
|
||||
service.Register("/entry/{datapoint}", false, false, entryDatapoint)
|
||||
|
||||
err = service.Start()
|
||||
if err != nil {
|
||||
logger.Error("webserver", "error", we.Wrap(err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
} // }}}
|
||||
|
||||
func sqlProvider(dbname string, version int) (sql []byte, found bool) { // {{{
|
||||
var err error
|
||||
sql, err = sqlFS.ReadFile(fmt.Sprintf("sql/%05d.sql", version))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
found = true
|
||||
return
|
||||
} // }}}
|
||||
func logHandler(err we.Error) { // {{{
|
||||
j, _ := json.Marshal(err)
|
||||
logFile.Write(j)
|
||||
logFile.Write([]byte("\n"))
|
||||
} // }}}
|
||||
|
||||
func httpError(w http.ResponseWriter, err error) { // {{{
|
||||
resp := struct {
|
||||
OK bool
|
||||
Error string
|
||||
}{
|
||||
false,
|
||||
err.Error(),
|
||||
}
|
||||
j, _ := json.Marshal(resp)
|
||||
w.Write(j)
|
||||
} // }}}
|
||||
|
||||
func staticHandler(w http.ResponseWriter, r *http.Request, sess *session.T) { // {{{
|
||||
if flagDev && !reloadTemplates(w) {
|
||||
return
|
||||
}
|
||||
|
||||
if r.URL.Path == "/" {
|
||||
pageIndex(w, r, sess)
|
||||
return
|
||||
}
|
||||
|
||||
service.StaticHandler(w, r, sess)
|
||||
} // }}}
|
||||
func entryDatapoint(w http.ResponseWriter, r *http.Request, sess *session.T) { // {{{
|
||||
dpoint := r.PathValue("datapoint")
|
||||
value, _ := io.ReadAll(r.Body)
|
||||
|
||||
err := DatapointAdd(dpoint, value)
|
||||
if err != nil {
|
||||
httpError(w, we.Wrap(err).Log())
|
||||
return
|
||||
}
|
||||
|
||||
j, _ := json.Marshal(struct{ OK bool }{true})
|
||||
w.Write(j)
|
||||
} // }}}
|
||||
|
||||
func reloadTemplates(w http.ResponseWriter) bool { // {{{
|
||||
parsedTemplates = make(map[string]*template.Template)
|
||||
return true
|
||||
} // }}}
|
||||
func getComponentFilenames(efs fs.FS) (files []string, err error) { // {{{
|
||||
files = []string{}
|
||||
if err := fs.WalkDir(efs, "views/components", func(path string, d fs.DirEntry, err error) error {
|
||||
if d == nil {
|
||||
return nil
|
||||
}
|
||||
if d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
files = append(files, path)
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return files, nil
|
||||
} // }}}
|
||||
func getPage(layout, page string) (tmpl *template.Template, err error) { // {{{
|
||||
layoutFilename := fmt.Sprintf("views/layouts/%s.gotmpl", layout)
|
||||
pageFilename := fmt.Sprintf("views/pages/%s.gotmpl", page)
|
||||
|
||||
if tmpl, found := parsedTemplates[page]; found {
|
||||
return tmpl, nil
|
||||
}
|
||||
|
||||
filenames := []string{layoutFilename, pageFilename}
|
||||
filenames = append(filenames, componentFilenames...)
|
||||
logger.Info("template", "op", "parse", "layout", layout, "page", page, "filenames", filenames)
|
||||
if flagDev {
|
||||
parsedTemplates[page], err = template.ParseFS(os.DirFS("."), filenames...)
|
||||
} else {
|
||||
parsedTemplates[page], err = template.ParseFS(viewFS, filenames...)
|
||||
}
|
||||
tmpl = parsedTemplates[page]
|
||||
|
||||
if err != nil {
|
||||
err = we.Wrap(err).Log()
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
} // }}}
|
||||
|
||||
func pageIndex(w http.ResponseWriter, _ *http.Request, _ *session.T) { // {{{
|
||||
page := Page{
|
||||
LAYOUT: "main",
|
||||
PAGE: "index",
|
||||
}
|
||||
page.Render(w)
|
||||
} // }}}
|
||||
func pageProblems(w http.ResponseWriter, _ *http.Request, _ *session.T) { // {{{
|
||||
page := Page{
|
||||
LAYOUT: "main",
|
||||
PAGE: "problems",
|
||||
}
|
||||
|
||||
/*
|
||||
areas, err := ProblemsRetrieve()
|
||||
if err != nil {
|
||||
httpError(w, we.Wrap(err).Log())
|
||||
return
|
||||
}
|
||||
sort.SliceStable(areas, func(i, j int) bool {
|
||||
return areas[i].Name < areas[j].Name
|
||||
})
|
||||
|
||||
logger.Info("problems", "areas", areas)
|
||||
*/
|
||||
|
||||
page.Data = map[string]any{
|
||||
//"Areas": areas,
|
||||
}
|
||||
page.Render(w)
|
||||
return
|
||||
} // }}}
|
||||
func pageTriggers(w http.ResponseWriter, _ *http.Request, _ *session.T) { // {{{
|
||||
areas, err := TriggersRetrieve()
|
||||
if err != nil {
|
||||
httpError(w, we.Wrap(err).Log())
|
||||
return
|
||||
}
|
||||
|
||||
sort.SliceStable(areas, func(i, j int) bool {
|
||||
return areas[i].Name < areas[j].Name
|
||||
})
|
||||
|
||||
page := Page{
|
||||
LAYOUT: "main",
|
||||
PAGE: "triggers",
|
||||
Data: map[string]any{
|
||||
"Areas": areas,
|
||||
},
|
||||
}
|
||||
|
||||
page.Render(w)
|
||||
} // }}}
|
||||
func pageTriggerEdit(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{
|
||||
idStr := r.PathValue("id")
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
httpError(w, we.Wrap(err).Log())
|
||||
return
|
||||
}
|
||||
|
||||
var trigger Trigger
|
||||
trigger, err = TriggerRetrieve(id)
|
||||
if err != nil {
|
||||
httpError(w, we.Wrap(err).Log())
|
||||
return
|
||||
}
|
||||
|
||||
datapoints := make(map[string]Datapoint)
|
||||
for _, dpname := range trigger.Datapoints {
|
||||
dp, err := DatapointRetrieve(dpname)
|
||||
if err != nil {
|
||||
httpError(w, we.Wrap(err).Log())
|
||||
return
|
||||
}
|
||||
datapoints[dpname] = dp
|
||||
}
|
||||
|
||||
page := Page{
|
||||
LAYOUT: "main",
|
||||
PAGE: "trigger_edit",
|
||||
MENU: "triggers",
|
||||
Label: "Trigger",
|
||||
Icon: "triggers",
|
||||
Data: map[string]any{
|
||||
"Trigger": trigger,
|
||||
"Datapoints": datapoints,
|
||||
},
|
||||
}
|
||||
|
||||
page.Render(w)
|
||||
} // }}}
|
||||
func pageTriggerUpdate(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{
|
||||
idStr := r.PathValue("id")
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
httpError(w, we.Wrap(err).Log())
|
||||
return
|
||||
}
|
||||
|
||||
var trigger Trigger
|
||||
trigger, err = TriggerRetrieve(id)
|
||||
if err != nil {
|
||||
httpError(w, we.Wrap(err).Log())
|
||||
return
|
||||
}
|
||||
|
||||
trigger.Name = r.FormValue("name")
|
||||
trigger.Expression = r.FormValue("expression")
|
||||
err = trigger.Update()
|
||||
if err != nil {
|
||||
httpError(w, we.Wrap(err).Log())
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Add("Location", fmt.Sprintf("/trigger/edit/%d", id))
|
||||
w.WriteHeader(302)
|
||||
} // }}}
|
||||
func pageTriggerRun(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{
|
||||
idStr := r.PathValue("id")
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
httpError(w, we.Wrap(err).Log())
|
||||
return
|
||||
}
|
||||
|
||||
var trigger Trigger
|
||||
trigger, err = TriggerRetrieve(id)
|
||||
if err != nil {
|
||||
httpError(w, we.Wrap(err).Log())
|
||||
return
|
||||
}
|
||||
|
||||
expr, _ := io.ReadAll(r.Body)
|
||||
trigger.Expression = string(expr)
|
||||
|
||||
datapoints := make(map[string]Datapoint)
|
||||
for _, dpname := range trigger.Datapoints {
|
||||
dp, err := DatapointRetrieve(dpname)
|
||||
if err != nil {
|
||||
httpError(w, we.Wrap(err).Log())
|
||||
return
|
||||
}
|
||||
datapoints[dpname] = dp
|
||||
}
|
||||
|
||||
resp := struct {
|
||||
OK bool
|
||||
Output any
|
||||
}{
|
||||
OK: true,
|
||||
}
|
||||
resp.Output, err = trigger.Run(datapoints)
|
||||
if err != nil {
|
||||
we.Wrap(err).Log()
|
||||
httpError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
j, _ := json.Marshal(resp)
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
w.Write(j)
|
||||
} // }}}
|
||||
func pageConfiguration(w http.ResponseWriter, _ *http.Request, _ *session.T) { // {{{
|
||||
areas, err := AreaRetrieve()
|
||||
if err != nil {
|
||||
httpError(w, we.Wrap(err).Log())
|
||||
return
|
||||
}
|
||||
|
||||
sort.SliceStable(areas, func(i, j int) bool {
|
||||
return areas[i].Name < areas[j].Name
|
||||
})
|
||||
|
||||
page := Page{
|
||||
LAYOUT: "main",
|
||||
PAGE: "configuration",
|
||||
Data: map[string]any{
|
||||
"Areas": areas,
|
||||
},
|
||||
}
|
||||
|
||||
page.Render(w)
|
||||
} // }}}
|
||||
Loading…
Add table
Add a link
Reference in a new issue