package main import ( "database/sql" "encoding/json" "flag" "fmt" "html" "html/template" "io/ioutil" "log" "net/http" "strings" "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"` } Context struct { Mappings map[int]map[int]MapEntry Checks []check } MapEntry struct { Title string Color string } check struct { NodeName string CommandName string CheckID int64 MappingId int State int Enabled bool Notice sql.NullString NextTime time.Time Msg string } ) 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.HandleFunc("/unhandled/checks", showChecks) http.HandleFunc("/unhandled/hosts", showUnhandledHosts) http.HandleFunc("/unhandled/groups", showUnhandledGroups) 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 } ref, found := r.Header["Referrer"] if found { w.Header()["Location"] = ref } else { w.Header()["Location"] = []string{"/"} } w.WriteHeader(http.StatusSeeOther) return } func showChecks(w http.ResponseWriter, r *http.Request) { query := SQLShowChecks if strings.HasPrefix(r.URL.Path, "/unhandled") { query = SQLShowUnhandledChecks } rows, err := DB.Query(query) 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 := []check{} for rows.Next() { c := check{} err := rows.Scan(&c.CheckID, &c.NodeName, &c.CommandName, &c.MappingId, &c.State, &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 := template.New("checklist") tmpl.Funcs(Funcs) tmpl, err = tmpl.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 } con := Context{ Checks: checks, } 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.Execute(w, con); err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("problem with a template")) log.Printf("could not execute template: %s", err) return } return } func showUnhandledHosts(w http.ResponseWriter, r *http.Request) { } func showUnhandledGroups(w http.ResponseWriter, r *http.Request) { rows, err := DB.Query(SQLShowUnhandledGroups) 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 { GroupName string NodeName string State int } checks := []check{} for rows.Next() { c := check{} err := rows.Scan(&c.GroupName, &c.NodeName, &c.State) 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(TmplUnhandledGroups) 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 } func loadMappings(c *Context) error { c.Mappings = map[int]map[int]MapEntry{} rows, err := DB.Query(SQLShowMappings) if err != nil { return err } for rows.Next() { if rows.Err() != nil { return rows.Err() } var ( mapId int target int title string color string ) if err := rows.Scan(&mapId, &target, &title, &color); err != nil { return err } ma, found := c.Mappings[mapId] if !found { ma = map[int]MapEntry{} c.Mappings[mapId] = ma } ma[target] = MapEntry{Title: title, Color: color} } return nil } var ( SQLShowMappings = `select mapping_id, target, title, color from mapping_level` SQLShowChecks = `select c.id, n.name, co.name, ac.mapping_id, ac.states[1] as state, 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;` SQLShowUnhandledChecks = `select c.id, n.name, co.name, ac.mapping_id, ac.states[1] as state, 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 where ac.states[1] > 0 order by n.name, co.name;` SQLShowUnhandledGroups = `select g.name, n.name, max(ac.state[1]) from groups g join nodes_groups ng on g.id = ng.group_id join nodes n on ng.node_id = n.id join checks c on n.id = c.node_id join active_checks ac on c.id = ac.check_id where ac.states[1] > 0 group by g.name, n.name;` ) var ( TmplCheckList = ` check list
{{ $current := "" }} {{ $mapping := .Mappings }} {{ range .Checks }} {{ end }}
hoststatusnext checkmessage
{{ if ne $current .NodeName }}{{ $current = .NodeName }}{{ .NodeName }}{{ end }} {{ .CommandName }} - {{ (index $mapping .MappingId .State).Title }} {{ .NextTime.Format "2006.01.02 15:04:05" }}
{{ .Msg }}
` TmplUnhandledGroups = `TODO` Funcs = template.FuncMap{ "sub": func(base, amount int) int { return base - amount }, } )