From 3b8e27706b2b0d623d2c162c1cd24f5f5806140a Mon Sep 17 00:00:00 2001 From: Gibheer Date: Tue, 20 Nov 2018 21:48:16 +0100 Subject: monfront - add initial version This version can already show the list of checks, their last state and add ways to manipulate the check or active check. --- cmd/monfront/main.go | 254 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 254 insertions(+) create mode 100644 cmd/monfront/main.go (limited to 'cmd/monfront/main.go') diff --git a/cmd/monfront/main.go b/cmd/monfront/main.go new file mode 100644 index 0000000..544d4f3 --- /dev/null +++ b/cmd/monfront/main.go @@ -0,0 +1,254 @@ +package main + +import ( + "database/sql" + "encoding/json" + "flag" + "fmt" + "html" + "html/template" + "io/ioutil" + "log" + "net/http" + "time" + + "github.com/lib/pq" +) + +var ( + configPath = flag.String("config", "monfront.conf", "path to the config file") + DB *sql.DB +) + +type ( + Config struct { + DB string `json:"db"` + Listen string `json:"listen"` + } +) + +func main() { + flag.Parse() + + raw, err := ioutil.ReadFile(*configPath) + if err != nil { + log.Fatalf("could not read config: %s", err) + } + config := Config{} + if err := json.Unmarshal(raw, &config); err != nil { + log.Fatalf("could not parse config: %s", err) + } + + db, err := sql.Open("postgres", config.DB) + if err != nil { + log.Fatalf("could not open database connection: %s", err) + } + DB = db + + http.HandleFunc("/", showChecks) + http.HandleFunc("/action", checkAction) + http.ListenAndServe(config.Listen, nil) +} + +func checkAction(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" { + w.WriteHeader(http.StatusMethodNotAllowed) + w.Write([]byte("method is not supported")) + return + } + if err := r.ParseForm(); err != nil { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintf(w, "could not parse parameters: %s", err) + return + } + checks := r.PostForm["checks"] + action := r.PostForm["action"] + if len(action) == 0 || action[0] == "" || len(checks) == 0 { + w.Header()["Location"] = []string{"/"} + w.WriteHeader(http.StatusSeeOther) + return + } + setTable := "checks" + setClause := "" + switch action[0] { + case "mute": + setClause = "notify = false" + case "unmute": + setClause = "notify = true" + case "enable": + setClause = "enabled = true" + case "disable": + setClause = "enabled = false" + case "reschedule": + setClause = "next_time = now()" + setTable = "active_checks" + case "ack": + setClause = "acknowledged = true" + setTable = "active_checks" + case "comment": + if len(r.PostForm["comment"]) == 0 { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintf(w, "no comment sent") + return + } + comment := r.PostForm["comment"][0] + _, err := DB.Exec( + "update active_checks set notice = $2 where check_id = any ($1::int[]);", + pq.Array(&checks), + html.EscapeString(comment)) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "could not store changes") + log.Printf("could not adjust checks %#v: %s", checks, err) + return + } + w.Header()["Location"] = []string{"/"} + w.WriteHeader(http.StatusSeeOther) + return + default: + w.WriteHeader(http.StatusNotFound) + fmt.Fprintf(w, "unknown action %s", action) + return + } + whereColumn := "id" + if setTable == "active_checks" { + whereColumn = "check_id" + } + + _, err := DB.Exec("update "+setTable+" set "+setClause+" where "+whereColumn+" = any ($1::int[]);", pq.Array(&checks)) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "could not store changes") + log.Printf("could not adjust checks %#v: %s", checks, err) + return + } + w.Header()["Location"] = []string{"/"} + w.WriteHeader(http.StatusSeeOther) + return +} + +func showChecks(w http.ResponseWriter, r *http.Request) { + rows, err := DB.Query(SQLShowChecks) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("problems with the database")) + log.Printf("could not get check list: %s", err) + return + } + + type check struct { + Name string + CommandName string + CheckID int64 + State int + Notify bool + Enabled bool + Notice sql.NullString + NextTime time.Time + Msg string + } + checks := []check{} + for rows.Next() { + c := check{} + err := rows.Scan(&c.CheckID, &c.Name, &c.CommandName, &c.State, &c.Notify, &c.Enabled, &c.Notice, &c.NextTime, &c.Msg) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("problems with the database")) + log.Printf("could not get check list: %s", err) + return + } + checks = append(checks, c) + } + tmpl, err := template.New("checklist").Parse(TmplCheckList) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("problems with a template")) + log.Printf("could not parse template: %s", err) + return + } + w.Header()["Content-Type"] = []string{"text/html"} + if err := tmpl.Execute(w, checks); err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("problem with a template")) + log.Printf("could not execute template: %s", err) + return + } + return +} + +var ( + SQLShowChecks = `select c.id, n.name, co.name, ac.states[1] as state, ac.notify, + ac.enabled, ac.notice, ac.next_time, ac.msg + from active_checks ac + join checks c on ac.check_id = c.id + join nodes n on c.node_id = n.id + join commands co on c.command_id = co.id + order by n.name, co.name;` +) + +var ( + TmplCheckList = ` + + + check list + + + +
+
+ +
+ + +
+
+ + +
+ +
+ + + + + {{ range . }} + + + + + + + + {{ end }} + +
hoststatusnext checkmessage
{{ .Name }}{{ .CommandName }} - {{ .State }}{{ .NextTime.Format "2006.01.02 15:04:05" }}
{{ .Msg }}
+
+ + +` +) -- cgit v1.2.3-70-g09d2