aboutsummaryrefslogtreecommitdiff
path: root/cmd/monfront/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/monfront/main.go')
-rw-r--r--cmd/monfront/main.go874
1 files changed, 0 insertions, 874 deletions
diff --git a/cmd/monfront/main.go b/cmd/monfront/main.go
deleted file mode 100644
index 57904a5..0000000
--- a/cmd/monfront/main.go
+++ /dev/null
@@ -1,874 +0,0 @@
-package main
-
-import (
- "database/sql"
- "encoding/json"
- "flag"
- "fmt"
- "html"
- "html/template"
- "io/ioutil"
- "log"
- "net/http"
- "os"
- "strings"
- "time"
-
- "github.com/lib/pq"
-)
-
-var (
- configPath = flag.String("config", "monfront.conf", "path to the config file")
- DB *sql.DB
- Tmpl *template.Template
-)
-
-type (
- Config struct {
- DB string `json:"db"`
- Listen string `json:"listen"`
- }
-
- Context struct {
- Title string
- CurrentPath string
- Error string
- Mappings map[int]map[int]MapEntry
- Checks []check
- CheckDetails *checkDetails
- Groups []group
- Unhandled bool // set this flag when unhandled was called
- }
-
- MapEntry struct {
- Title string
- Color string
- }
-
- check struct {
- NodeId int
- NodeName string
- CommandName string
- CheckID int64
- MappingId int
- State int
- Enabled bool
- Notify bool
- Notice sql.NullString
- NextTime time.Time
- 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
- State int
- Output string
- Inserted time.Time
- Sent pq.NullTime
- NotifierName string
- MappingId int
- }
-
- group struct {
- GroupId int
- Name string
- NodeId int
- NodeName string
- State int
- MappingId int
- }
-)
-
-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
-
- tmpl := template.New("main")
- tmpl.Funcs(Funcs)
- for k, val := range Templates {
- template.Must(tmpl.New(k).Parse(val))
- }
- for k, val := range AdminTemplates {
- template.Must(tmpl.New("admin-" + k).Parse(val))
- }
- Tmpl = tmpl
-
- http.HandleFunc("/", showChecks)
- http.HandleFunc("/admin/", showAdmin)
- http.HandleFunc("/admin/action", adminAction)
- http.HandleFunc("/static/", showStatic)
- http.HandleFunc("/check", showCheck)
- http.HandleFunc("/checks", showChecks)
- http.HandleFunc("/groups", showGroups)
- http.HandleFunc("/action", checkAction)
- http.HandleFunc("/unhandled/checks", showChecks)
- http.HandleFunc("/unhandled/groups", showGroups)
- http.ListenAndServe(config.Listen, nil)
-}
-
-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"))
- return
- }
- if err := r.ParseForm(); err != nil {
- w.WriteHeader(http.StatusBadRequest)
- fmt.Fprintf(w, "could not parse parameters: %s", err)
- return
- }
- ref, found := r.Header["Referer"]
- if found {
- w.Header()["Location"] = ref
- } else {
- w.Header()["Location"] = []string{"/"}
- }
- checks := r.PostForm["checks"]
- action := r.PostForm["action"]
- if len(action) == 0 || action[0] == "" || len(checks) == 0 {
- w.WriteHeader(http.StatusSeeOther)
- return
- }
- setTable := "checks"
- setClause := ""
- switch action[0] {
- case "mute":
- setTable = "checks_notify"
- setClause = "enabled = false"
- case "unmute":
- setTable = "checks_notify"
- setClause = "enabled = true"
- case "enable":
- setClause = "enabled = true, updated = now()"
- case "disable":
- setClause = "enabled = false, updated = now()"
- case "reschedule":
- setClause = "next_time = now()"
- setTable = "active_checks"
- case "ack":
- setClause = "acknowledged = true"
- setTable = "active_checks"
-
- hostname, err := os.Hostname()
- if err != nil {
- log.Printf("could not resolve hostname: %s", err)
- con.Error = "could not resolve hostname"
- returnError(http.StatusInternalServerError, con, w)
- return
- }
- if _, err := DB.Exec(`insert into notifications(check_id, states, output, mapping_id, notifier_id, check_host)
- select ac.check_id, 0 || states[1:array_length(states, 1)], 'check acknowledged', ac.mapping_id,
- cn.notifier_id, $2
- from checks_notify cn
- join active_checks ac on cn.check_id = ac.check_id
- where cn.check_id = any ($1::int[])`, pq.Array(&checks), &hostname); err != nil {
- log.Printf("could not acknowledge check: %s", err)
- con.Error = "could not acknowledge check"
- returnError(http.StatusInternalServerError, con, w)
- return
- }
- 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.WriteHeader(http.StatusSeeOther)
- return
- default:
- con.Error = fmt.Sprintf("requested action '%s' does not exist", action[0])
- returnError(http.StatusNotFound, con, w)
- return
- }
- whereColumn := "id"
- if setTable == "active_checks" || setTable == "checks_notify" {
- 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.WriteHeader(http.StatusSeeOther)
- return
-}
-
-func showChecks(w http.ResponseWriter, r *http.Request) {
- con := Context{}
- query := `select c.id, n.id, n.name, co.name, ac.mapping_id, ac.states[1] as state,
- ac.enabled, ac.notice, ac.next_time, ac.msg,
- case when cn.check_id is null then false else true end as notify_enabled
- 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
- left join ( select distinct check_id from checks_notify where enabled = true) cn on c.id = cn.check_id`
- where := []string{}
- idx := 0
- params := []interface{}{}
- if id, found := r.URL.Query()["group_id"]; found {
- query += ` join nodes_groups ng on n.id = ng.node_id`
- idx += 1
- where = append(where, fmt.Sprintf(`ng.group_id = $%d::int`, idx))
- params = append(params, id[0])
- }
- if strings.HasPrefix(r.URL.Path, "/unhandled") {
- where = append(where, `ac.states[1] > 0 and ac.acknowledged = false and ac.enabled = true`)
- con.Unhandled = true
- }
- if search, found := r.URL.Query()["search"]; found {
- idx += 1
- // Add the search for nodes. As hostnames or FQDNs are really weird, the
- // string needs to be split up by some characters. The input string needs
- // to be split up too, so all is done here.
- // TODO move this into a proper index and add more to search.
- where = append(where, fmt.Sprintf(
- `to_tsvector('english', regexp_replace(n.name, '[.-/]', ' ', 'g')) @@
- to_tsquery('english', regexp_replace($%d, '[.-/]', ' & ', 'g'))`, idx))
- params = append(params, search[0])
- }
- if id, found := r.URL.Query()["node_id"]; found {
- idx += 1
- where = append(where, fmt.Sprintf("n.id = $%d::int", idx))
- params = append(params, id[0])
- }
- if id, found := r.URL.Query()["command_id"]; found {
- idx += 1
- where = append(where, fmt.Sprintf("co.id = $%d::int", idx))
- params = append(params, id[0])
- }
- if id, found := r.URL.Query()["check_id"]; found {
- idx += 1
- where = append(where, fmt.Sprintf("c.id = $%d::int", idx))
- params = append(params, id[0])
- }
- if len(where) > 0 {
- query += " where " + strings.Join(where, " and ")
- }
- if strings.HasPrefix(r.URL.Path, "/unhandled") {
- query += ` order by ac.states[1] desc, n.name, co.name`
- } else {
- query += ` order by n.name, co.name`
- }
- rows, err := DB.Query(query, params...)
- 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.NodeId, &c.NodeName, &c.CommandName, &c.MappingId, &c.State, &c.Enabled, &c.Notice, &c.NextTime, &c.Msg, &c.Notify)
- 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)
- }
- con.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)
- return
- }
- w.Header()["Content-Type"] = []string{"text/html"}
- if err := Tmpl.ExecuteTemplate(w, "checklist", 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 showGroups(w http.ResponseWriter, r *http.Request) {
- con := Context{}
- query := `select
- group_id,
- group_name,
- node_id,
- node_name,
- mapping_id,
- state
-from (
- select
- g.id group_id,
- g.name group_name,
- n.id node_id,
- n.name node_name,
- ac.states[1] state,
- ac.mapping_id,
- ac.acknowledged,
- row_number() over (partition by c.node_id order by ac.states[1] desc) maxstate
- 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
- %s
- order by g.name, n.name
-) groups
-where maxstate = 1`
- if strings.HasPrefix(r.URL.Path, "/unhandled") {
- query = fmt.Sprintf(query, `where ac.states[1] != 0 and acknowledged = false`)
- con.Unhandled = true
- } else {
- query = fmt.Sprintf(query, "")
- }
-
- 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
- }
-
- groups := []group{}
- for rows.Next() {
- g := group{}
- err := rows.Scan(&g.GroupId, &g.Name, &g.NodeId, &g.NodeName, &g.MappingId, &g.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
- }
- groups = append(groups, g)
- }
- con.Groups = groups
- 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, "grouplist", con); err != nil {
- w.WriteHeader(http.StatusInternalServerError)
- w.Write([]byte("problem with a template"))
- log.Printf("could not execute template: %s", err)
- return
- }
- 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, ac.next_time
- 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, &cd.NextTime)
- 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
- }
-
- query = `select n.id, states[1], output, inserted, sent, no.name, n.mapping_id
- from notifications n
- join notifier no on n.notifier_id = no.id
- where check_id = $1::bigint
- order by inserted desc
- limit 500`
- rows, err := DB.Query(query, cd.Id)
- if err != nil {
- log.Printf("could not load notifications: %s", err)
- con.Error = "could not load notification information"
- returnError(http.StatusInternalServerError, con, w)
- return
- }
- cd.Notifications = []notification{}
- for rows.Next() {
- if err := rows.Err(); err != nil {
- log.Printf("could not load notifications: %s", err)
- con.Error = "could not load notification information"
- returnError(http.StatusInternalServerError, con, w)
- return
- }
- no := notification{}
- if err := rows.Scan(&no.Id, &no.State, &no.Output, &no.Inserted,
- &no.Sent, &no.NotifierName, &no.MappingId); err != nil {
- log.Printf("could not scan notifications: %s", err)
- con.Error = "could not load notification information"
- returnError(http.StatusInternalServerError, con, w)
- return
- }
- cd.Notifications = append(cd.Notifications, no)
- }
-
- 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 returnError(status int, con interface{}, 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)
- 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
-}
-
-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`
-)
-
-var (
- Templates = map[string]string{
- "header": `<!doctype html>
-<html>
- <head>
- <title>{{ .Title }}</title>
- <meta name="referrer" content="same-origin">
- <link rel="shortcut icon" href="/static/favicon" />
- <style type="text/css">
- * { font-size: 100%; }
- body { display: flex; flex-direction: column; padding: 0; margin: 0; }
- #mainmenu { background: #3a5f78; }
- #mainmenu ul {
- padding: 0;
- display: flex;
- flex-direction: row;
- align-items: stretch;
- align-content: center; }
- #mainmenu .submenu { border-left: 0.1em solid black; }
- #mainmenu li { list-style-type: none; }
- .submenu .header {
- text-align: center;
- font-weight: bold;
- color: #ff9000;
- padding: 0.5em 0.5em;
- display: block; }
- #mainmenu a, #mainmenu a:visited, #mainmenu a:active, #mainmenu a:hover {
- color: #ff9000;
- padding: 0.25em 0.5em;
- display: block; }
- #mainmenu a:hover, #mainmenu a:active { color: #eeeeee; }
- #mainmenu ul ul a { margin-left: 0.5em; }
- #mainmenu form * { display: block; margin: 0.25em 0.5em; }
- form section {
- display: flex;
- flex-direction: row;
- justify-content: center;
- align-items: flex-start;
- }
- form nav { order: 1; }
- form content { order: 2; flex-grow: 1; border-left: 0.15em solid #dddddd; }
- form nav { display: flex; flex-direction: column; }
- form nav > * { margin: 0.5em; }
- table { width: 100%; }
- table tr:nth-child(odd) { background: #eeeeee; }
- table tr.selected:nth-child(odd) { background: #afbfd4; }
- table tr.selected:nth-child(even) { background: #cddbec; }
- table tr:hover { background: #dfdfdf; }
- table th { background: #cccccc; color: #3a5f78; }
- table td, table th { text-align: center; }
- table code { font-size: 75%; }
- table td.disabled { text-decoration: line-through; }
- .icon {
- display: inline-block;
- height: 1em;
- margin: 0;
- width: 1em;
- vertical-align: bottom;
- margin-right: 0.5em;
- background-size: contain;
- }
- .mute { background-image: url(/static/icon-mute); }
- .detail > div { display: grid; grid-template-columns: 25% auto; }
- .detail > div:hover { background: #dfdfdf; }
- .error { padding: 0.5em; background: #ffc6c6; border: 1px solid red; }
- /* state background colors */
- {{ range $mapId, $mapping := .Mappings -}}
- {{ range $target, $val := $mapping -}}
- .state-{{ $mapId }}-{{ $target }} { background: {{ $val.Color }}; }
- {{ end -}}
- {{ end -}}
- </style>
- <script>
- setTimeout(function() { if (document.activeElement.tagName == "BODY") { location.reload(true) } }, 30000)
- </script>
- </head>
- <body>
- <nav id="mainmenu">
- <ul>
- <li class="submenu">
- <span class="header">main</span>
- <ul>
- <li><a href="/">home</a></li>
- <li><a href="/admin">admin</a></li>
- </ul>
- </li>
- <li class="submenu">
- <span class="header">all</span>
- <ul>
- <li><a href="/checks">checks</a></li>
- <li><a href="/groups">groups</a></li>
- </ul>
- </li>
- <li class="submenu">
- <span class="header">unhandled</span>
- <ul>
- <li><a href="/unhandled/checks">checks</a></li>
- <li><a href="/unhandled/groups">groups</a></li>
- </ul>
- </li>
- <li class="submenu">
- <form action="/checks" method="get">
- <input name="search" placeholder="search" />
- <button type="submit">search</button>
- </form>
- </li>
- <li class="submenu"><span class="header">{{ now.Format "2006.01.02 15:04:05" }}</span></li>
- </ul>
- </nav>
- {{ if .Error }}<div class="error">{{ .Error }}</div>{{ end }}`,
- "footer": `<script>
- function row_head_click_event(event) {
- check = false;
- current = event.target;
- while (current != null) {
- if (current.nodeName == 'TABLE') {
- break;
- }
- if (current.nodeName == 'TR') {
- check = !current.children[0].children[0].checked;
- current.children[0].children[0].checked = check;
- }
- current = current.parentNode;
- }
- lines = current.children[1].children
- for (i = 0; i < lines.length; i++) {
- select_row(event, lines[i], lines[i].children[0].children[0], check);
- }
- }
- function row_click_event(event) {
- if (event.target.nodeName == 'INPUT') {
- return;
- }
- current = event.target;
- while (current = current.parentNode) {
- if (current.nodeName == 'BODY') {
- break;
- }
- if (current.nodeName != 'TR') {
- continue;
- }
- e = current.children[0].children[0];
- check = !e.checked;
- select_row(event, current, e, check);
- break;
- }
- }
- function select_row(event, row, input, check) {
- if (input != event.target) {
- input.checked = check;
- }
- if (input.checked) {
- row.classList.add("selected");
- } else {
- row.classList.remove("selected");
- }
- input.focus();
- }
-
- for (selector of ['thead > tr', 'thead input']) {
- els = document.querySelectorAll(selector);
- for (i = 0; i < els.length; i++) {
- els[i].addEventListener('click', {handleEvent: row_head_click_event});
- }
- }
- for (selector of ['tbody > tr', 'tbody input']) {
- els = document.querySelectorAll(selector);
- for (i = 0; i < els.length; i++) {
- els[i].addEventListener('click', {handleEvent: row_click_event});
- }
- }</script></body></html>`,
- "checklist": `{{ template "header" . }}
- <form method="post" action="/action">
- <section>
- <nav>
- <div class="option">
- <label for="action">Action</label>
- <select name="action">
- <option value="reschedule">run now</option>
- <option value="mute">mute</option>
- <option value="unmute">unmute</option>
- <option value="ack">acknowledge</option>
- <option value="disable">disable</option>
- <option value="enable">enable</option>
- <option value="comment">comment</option>
- </select>
- </div>
- <div class="option">
- <label for="comment">comment</label>
- <input name="comment" />
- </div>
- <button type="submit">submit</button>
- </nav>
- <content>
- <table>
- <thead><tr><th><input type="checkbox" title="select all" /></th><th>host</th><th>status</th><th>next check</th><th>message</th></tr></thead>
- <tbody>
- {{ $current := "" }}
- {{ $mapping := .Mappings }}
- {{ range .Checks }}
- <tr>
- <td><input type="checkbox" name="checks" value="{{ .CheckID }}" /></td>
- <td>{{ if ne $current .NodeName }}{{ $current = .NodeName }}<a href="/checks?node_id={{ .NodeId }}">{{ .NodeName }}</a>{{ end }}</td>
- <td class="state-{{ .MappingId }}-{{ .State }}">{{ if ne .Notify true }}<span class="icon mute"></span>{{ end }}<a href="/check?check_id={{ .CheckID }}">{{ .CommandName }}</a> - {{ (index $mapping .MappingId .State).Title }}</td>
- <td {{ if ne .Enabled true }}title="This check is disabled." class="disabled"{{ end }}>{{ .NextTime.Format "2006.01.02 15:04:05" }} - in {{ in .NextTime }}</td>
- <td><code>{{ .Msg }}</code></td>
- </tr>
- {{ end }}
- </tbody>
- </table>
- </content>
- </form>
- {{ template "footer" . }}`,
- "checkformheader": `<form method="post" action="/action">
- <section>
- <nav>
- <div class="option">
- <label for="action">Action</label>
- <select name="action">
- <option value="reschedule">run now</option>
- <option value="mute">mute</option>
- <option value="unmute">unmute</option>
- <option value="ack">acknowledge</option>
- <option value="disable">disable</option>
- <option value="enable">enable</option>
- <option value="comment">comment</option>
- </select>
- </div>
- <div class="option">
- <label for="comment">comment</label>
- <input name="comment" />
- </div>
- <button type="submit">submit</button>
- </nav>`,
- "checkformfooter": `</section></form>`,
- "grouplist": `{{ template "header" . }}
- {{ template "checkformheader" . }}
- <content>
- <table>
- <thead><tr><th></th><th>group</th><th>host</th><th>worst state</th></tr></thead>
- <tbody>
- {{ $current := "" }}
- {{ $mapping := .Mappings }}
- {{ range .Groups }}
- <tr>
- <td><input type="checkbox" name="nodes" value="{{ .NodeId }}" /></td>
- <td>{{ if ne $current .Name }}{{ $current = .Name }}<a href="{{ if $.Unhandled }}/unhandled{{ end }}/checks?group_id={{ .GroupId }}">{{ .Name }}</a>{{ end }}</td>
- <td><a href="/checks?node_id={{ .NodeId }}">{{ .NodeName }}</a></td>
- <td class="state-{{ .MappingId }}-{{ .State }}">{{ (index $mapping .MappingId .State).Title }}</td>
- </tr>
- {{ end }}
- </tbody>
- </table>
- </content>
- {{ template "checkformfooter" . }}
- {{ template "footer" . }}`,
- "check": `{{ template "header" . }}
- {{ template "checkformheader" . }}
- <content class="details">
- {{ $mapping := .Mappings }}
- {{ with .CheckDetails }}
- <input type="hidden" name="checks" value="{{ .Id }}" />
- <article class="detail">
- <h1>check</h1>
- <div><span class="label">current state</span><span class="value state-{{ .MappingId }}-{{ index .States 0 }}">{{ (index $mapping .MappingId (index .States 0) ).Title }}</span></div>
- <div><span class="label">current notice</span><span class="value">{{ if .Notice }}{{ .Notice.String }}{{ end }}</span></div>
- <div><span class="label">Message</span><span class="value">{{ .Message }}</span></div>
- <div><span class="label">enabled</span><span class="value">{{ .Enabled }}</span></div>
- <div><span class="label">updated</span><span class="value">{{ .Updated.Format "2006.01.02 15:04:05" }}</span></div>
- <div><span class="label">next check</span><span class="value">{{ .NextTime.Format "2006.01.02 15:04:05" }}</span></div>
- <div><span class="label">last refresh</span><span class="value">{{ .LastRefresh.Format "2006.01.02 15:04:05" }}</span></div>
- <div><span class="label">mapping</span><span class="value">{{ .MappingId }}</span></div>
- </article>
- <article class="detail">
- <h1>node <a href="/checks?node_id={{ .NodeId }}">{{ .NodeName }}</a></h1>
- <div><span class="label">Message</span><span class="value">{{ .NodeMessage }}</span></div>
- </article>
- <article class="detail">
- <h1>command {{ .CommandName }}</h1>
- <div><span class="label">Message</span><span class="value">{{ .CommandMessage }}</span></div>
- <div><span class="label">command line</span><span class="value"><code>{{ join .CommandLine " " }}</code></span></div>
- </article>
- <article>
- <h1>notifications</h1>
- <table>
- <thead><tr><th>notifier</th><th>state</th><th>created</th><th>sent</th><th>output</th></thead>
- <tbody>
- {{ range .Notifications -}}
- <tr>
- <td>{{ .NotifierName }}</td>
- <td class="state-{{ .MappingId }}-{{ .State }}">{{ (index $mapping .MappingId .State).Title }}</td>
- <td>{{ .Inserted.Format "2006.01.02 15:04:05" }}</td>
- <td>{{ if .Sent.Valid }}{{ .Sent.Time.Format "2006.01.02 15:04:05" }}{{ end }}</td>
- <td>{{ .Output }}</td>
- </tr>
- {{ end -}}
- </tbody>
- </table>
- </article>
- {{ end }}
- </content>
- {{ template "checkformfooter" . }}
- {{ template "footer" . }}`,
- }
- Static = map[string]string{
- "icon-mute": `<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 35.3 35.3" version="1.1"><title>Check is muted</title><style>.s0{fill:#191919;}</style><g transform="translate(0,-261.72223)"><path d="m17.6 261.7v35.3L5.3 284.7H0v-10.6l5.3 0zM30.2 273.1l-3.7 3.7-3.7-3.7-2.5 2.5 3.7 3.7-3.7 3.7 2.5 2.5 3.7-3.7 3.7 3.7 2.5-2.5-3.7-3.7 3.7-3.7z" fill="#191919"/></g></svg>`,
- "error": `{{ template "header" . }}{{ template "footer" . }}`,
- }
- TmplUnhandledGroups = `TODO`
- Funcs = template.FuncMap{
- "int": func(in int64) int { return int(in) },
- "sub": func(base, amount int) int { return base - amount },
- "in": func(t time.Time) time.Duration { return t.Sub(time.Now()).Round(1 * time.Second) },
- "now": func() time.Time { return time.Now() },
- "join": func(args []string, c string) string { return strings.Join(args, c) },
- }
-)