From 96d853fad68565257102fd2b722b01636f131852 Mon Sep 17 00:00:00 2001 From: Gibheer Date: Wed, 9 Jan 2019 20:17:49 +0100 Subject: monfront - add check detail view This adds a detail view for a single check. The purpose is to view notifications for this check alone and get the context information on the node it belongs to, the command and settings. --- cmd/monfront/main.go | 266 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 220 insertions(+), 46 deletions(-) (limited to 'cmd') diff --git a/cmd/monfront/main.go b/cmd/monfront/main.go index fd3d3ec..a3fd50d 100644 --- a/cmd/monfront/main.go +++ b/cmd/monfront/main.go @@ -29,10 +29,13 @@ type ( } Context struct { - Title string - Mappings map[int]map[int]MapEntry - Checks []check - Groups []group + Title string + CurrentPath string + Error string + Mappings map[int]map[int]MapEntry + Checks []check + CheckDetails *checkDetails + Groups []group } MapEntry struct { @@ -54,6 +57,42 @@ type ( Msg string } + checkDetails struct { + Id int64 + Message string + Enabled bool + Updated time.Time + LastRefresh time.Time + NextTime time.Time + MappingId int + MappingName string + NodeId int + NodeName string + NodeMessage string + CommandId int + CommandName string + CommandLine []string + CommandMessage string + States []int64 + Notice sql.NullString + Notifiers []notifier + Notifications []notification + } + + notifier struct { + Id int + Name string + Enabled bool + } + + notification struct { + Id int64 + States []int + Output string + Inserted time.Time + Sent time.Time + } + group struct { GroupId int Name string @@ -90,7 +129,10 @@ func main() { Tmpl = tmpl http.HandleFunc("/", showChecks) + http.HandleFunc("/static/", showStatic) http.HandleFunc("/check", showCheck) + http.HandleFunc("/group", showGroup) + http.HandleFunc("/node", showNode) http.HandleFunc("/checks", showChecks) http.HandleFunc("/groups", showGroups) http.HandleFunc("/action", checkAction) @@ -100,6 +142,7 @@ func main() { } func checkAction(w http.ResponseWriter, r *http.Request) { + con := Context{} if r.Method != "POST" { w.WriteHeader(http.StatusMethodNotAllowed) w.Write([]byte("method is not supported")) @@ -157,8 +200,8 @@ func checkAction(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusSeeOther) return default: - w.WriteHeader(http.StatusNotFound) - fmt.Fprintf(w, "unknown action %s", action) + con.Error = fmt.Sprintf("requested action '%s' does not exist", action[0]) + returnError(http.StatusNotFound, con, w) return } whereColumn := "id" @@ -194,7 +237,7 @@ func showChecks(w http.ResponseWriter, r *http.Request) { left join ( select distinct check_id from checks_notify where enabled = true) cn on c.id = cn.check_id` where := []string{} if strings.HasPrefix(r.URL.Path, "/unhandled") { - where = append(where, `ac.states[1] > 0 and ac.acknowledged = false`) + where = append(where, `ac.states[1] > 0 and ac.acknowledged = false and ac.enabled = true`) } idx := 0 params := []interface{}{} @@ -252,6 +295,7 @@ func showChecks(w http.ResponseWriter, r *http.Request) { Checks: checks, } if err := loadMappings(&con); err != nil { + con.Error = "could not load mapping data" w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("problem with the mappings")) log.Printf("could not load mappings: %s", err) @@ -267,10 +311,6 @@ func showChecks(w http.ResponseWriter, r *http.Request) { return } -// showCheck loads shows the notifications for a specific check. -func showCheck(w http.ResponseWriter, r *http.Request) { -} - func showGroups(w http.ResponseWriter, r *http.Request) { query := `select groupid, groupname, nodeid, nodename, mapping_id, state from ( @@ -326,6 +366,73 @@ func showGroups(w http.ResponseWriter, r *http.Request) { return } +// showCheck loads shows the notifications for a specific check. +func showCheck(w http.ResponseWriter, r *http.Request) { + cd := checkDetails{} + con := Context{CheckDetails: &cd} + id, found := r.URL.Query()["check_id"] + if !found { + con.Error = "no check given to view" + returnError(http.StatusNotFound, con, w) + return + } + query := `select c.id, c.message, c.enabled, c.updated, c.last_refresh, + m.id, m.name, n.id, n.name, n.message, co.id, co.Name, co.message, + ac.cmdline, ac.states, ac.msg + from checks c + join active_checks ac on c.id = ac.check_id + join nodes n on c.node_id = n.id + join commands co on c.command_id = co.id + join mappings m on ac.mapping_id = m.id + where c.id = $1::bigint` + err := DB.QueryRow(query, id[0]).Scan(&cd.Id, &cd.Message, &cd.Enabled, &cd.Updated, &cd.LastRefresh, + &cd.MappingId, &cd.MappingName, &cd.NodeId, &cd.NodeName, &cd.NodeMessage, + &cd.CommandId, &cd.CommandName, &cd.CommandMessage, + pq.Array(&cd.CommandLine), pq.Array(&cd.States), &cd.Notice) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("problems with the database")) + log.Printf("could not get check details for check id %s: %s", id[0], err) + return + } + + // TODO load the last couple notifications + + if err := loadMappings(&con); err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("problem with the mappings")) + log.Printf("could not load mappings: %s", err) + return + } + + w.Header()["Content-Type"] = []string{"text/html"} + if err := Tmpl.ExecuteTemplate(w, "check", con); err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("problem with a template")) + log.Printf("could not execute template: %s", err) + return + } +} +func showGroup(w http.ResponseWriter, r *http.Request) { + // TODO implement showing all nodes only from one group and its message? + return +} +func showNode(w http.ResponseWriter, r *http.Request) { + // TODO implement showing all checks from one node and its message? + return +} + +func returnError(status int, con Context, w http.ResponseWriter) { + w.Header()["Content-Type"] = []string{"text/html"} + w.WriteHeader(status) + if err := Tmpl.ExecuteTemplate(w, "error", con); err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("problem with a template")) + log.Printf("could not execute template: %s", err) + } + +} + func loadMappings(c *Context) error { c.Mappings = map[int]map[int]MapEntry{} rows, err := DB.Query(SQLShowMappings) @@ -356,6 +463,20 @@ func loadMappings(c *Context) error { return nil } +func showStatic(w http.ResponseWriter, r *http.Request) { + file := strings.TrimPrefix(r.URL.Path, "/static/") + raw, found := Static[file] + if !found { + w.WriteHeader(http.StatusNotFound) + w.Write([]byte("file does not exist")) + return + } + w.Header()["Content-Type"] = []string{"image/svg+xml"} + w.WriteHeader(http.StatusOK) + w.Write([]byte(raw)) + return +} + var ( SQLShowMappings = `select mapping_id, target, title, color from mapping_level` @@ -367,6 +488,7 @@ var ( {{ .Title }} +