Commit Diff


commit - 410b374583e5172b215020f0ad3db35a8635fa59
commit + 6c821dbc0890acc0ce1ff7c6a98523aa7fca0573
blob - /dev/null
blob + 568f6b0030d0fc1695d6a5f3ffd8225c5436ad88 (mode 644)
--- /dev/null
+++ cmd/checkweb/base.tmpl
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+	<meta charset="utf-8">
+	<meta name="viewport" content="width=device-width, initial-scale=1">
+	<link rel="stylesheet" href="https://the.missing.style">
+	<link href="https://fonts.bunny.net/css?family=source-sans-3:400,700|m-plus-code-latin:400,700" rel="stylesheet">
+	<title>{{block "title" .}}checkweb{{end}}</title>
+
+	<style>
+.unknown {
+	--box-bg: lavender;
+	--accent: rebeccapurple;
+}
+	</style>
+</head>
+<body>
+<header class="navbar">
+<nav class="contents">
+<a href="/">checkweb</a>
+<a href="/objects/services">Services</a>
+<a href="/objects/hosts">Hosts</a>
+<form class="f-row" action="/search">
+	<input type="search" placeholder="Search" name="filter">
+	<select name="type">
+		<option value="service">Services</option>
+		<option value="servicegroup">Service Groups</option>
+		<option value="host">Hosts</option>
+		<option value="hostgroup">Host Groups</option>
+	</select>
+</form>
+	</li>
+</ul>
+</nav>
+</header>
+<main>
+	{{block "content" .}}Hello, checkweb!{{end}}
+</main>
+<footer>
+</footer>
+</body>
+</html>
blob - /dev/null
blob + 314d44fcb0e30e753083d3401f3c1a27b8cb03d9 (mode 644)
--- /dev/null
+++ cmd/checkweb/checkweb.go
@@ -0,0 +1,168 @@
+// Command checkweb is a web application for...
+package main
+
+import (
+	"crypto/tls"
+	"errors"
+	"fmt"
+	"html/template"
+	"log"
+	"net/http"
+	"net/url"
+	"path"
+
+	"olowe.co/icinga"
+)
+
+type server struct {
+	tmpl struct {
+		services *template.Template
+		service  *template.Template
+		hosts    *template.Template
+		host     *template.Template
+		root     *template.Template
+	}
+	client *icinga.Client
+}
+
+func (srv *server) servicesHandler(w http.ResponseWriter, req *http.Request) {
+	filter := req.URL.Query().Get("filter")
+	services, err := srv.client.Services(filter)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		log.Print(err)
+		return
+	}
+	if err := srv.tmpl.services.Execute(w, services); err != nil {
+		log.Println("render services:", err)
+	}
+}
+
+func (srv *server) hostsHandler(w http.ResponseWriter, req *http.Request) {
+	filter := req.URL.Query().Get("filter")
+	hosts, err := srv.client.Hosts(filter)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		log.Print(err)
+		return
+	}
+	if err := srv.tmpl.hosts.Execute(w, hosts); err != nil {
+		log.Println("render hosts:", err)
+	}
+}
+
+func (srv *server) hostHandler(w http.ResponseWriter, req *http.Request) {
+	base := path.Base(req.URL.Path)
+	name, err := url.PathUnescape(base)
+	if err != nil {
+		msg := fmt.Sprintf("escape object name: %v", err)
+		log.Println(err)
+		http.Error(w, msg, http.StatusBadRequest)
+		return
+	}
+	host, err := srv.client.LookupHost(name)
+	if err != nil {
+		msg := fmt.Sprintf("lookup %s: %v", name, err)
+		code := http.StatusInternalServerError
+		if errors.Is(err, icinga.ErrNotExist) {
+			code = http.StatusNotFound
+		}
+		http.Error(w, msg, code)
+		return
+	}
+
+	if err := srv.tmpl.host.Execute(w, host); err != nil {
+		log.Println("render hosts:", err)
+	}
+}
+
+func (srv *server) serviceHandler(w http.ResponseWriter, req *http.Request) {
+	p := req.URL.Path
+	if req.URL.RawPath != "" {
+		p = req.URL.RawPath
+	}
+	base := path.Base(p)
+	name, err := url.PathUnescape(base)
+	if err != nil {
+		msg := fmt.Sprintf("escape object name: %v", err)
+		log.Println(err)
+		http.Error(w, msg, http.StatusBadRequest)
+		return
+	}
+	fmt.Println("looking up", name)
+	service, err := srv.client.LookupService(name)
+	if err != nil {
+		msg := fmt.Sprintf("lookup %s: %v", name, err)
+		code := http.StatusInternalServerError
+		if errors.Is(err, icinga.ErrNotExist) {
+			code = http.StatusNotFound
+		}
+		http.Error(w, msg, code)
+		return
+	}
+
+	if err := srv.tmpl.service.Execute(w, service); err != nil {
+		log.Println("render service:", err)
+	}
+}
+
+func (srv *server) searchHandler(w http.ResponseWriter, req *http.Request) {
+	var u url.URL
+	typ := req.URL.Query().Get("type")
+	switch typ {
+	case "host":
+		u.Path = "/objects/hosts"
+	case "service":
+		u.Path = "/objects/services"
+	}
+	v := url.Values{}
+	v.Set("filter", req.URL.Query().Get("filter"))
+	u.RawQuery = v.Encode()
+	http.Redirect(w, req, u.String(), http.StatusFound)
+}
+
+func (srv *server) rootHandler(w http.ResponseWriter, req *http.Request) {
+	if err := srv.tmpl.root.Execute(w, nil); err != nil {
+		log.Println(err)
+	}
+}
+
+var tFuncMap = template.FuncMap{
+	"pathescape": url.PathEscape,
+}
+
+func newTemplate(root *template.Template, files ...string) (*template.Template, error) {
+	t, err := root.Clone()
+	if err != nil {
+		return nil, err
+	}
+	return t.ParseFiles(files...)
+}
+
+func main() {
+	t := http.DefaultTransport.(*http.Transport)
+	t.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
+	c := http.DefaultClient
+	c.Transport = t
+	client, err := icinga.Dial("127.0.0.1:5665", "", "", c)
+	if err != nil {
+		log.Fatal(err)
+	}
+	srv := &server{
+		client: client,
+	}
+
+	srv.tmpl.root = template.Must(template.ParseFiles("base.tmpl")).Funcs(tFuncMap)
+	srv.tmpl.services = template.Must(newTemplate(srv.tmpl.root, "services.tmpl"))
+	srv.tmpl.service = template.Must(newTemplate(srv.tmpl.root, "service.tmpl"))
+	srv.tmpl.hosts = template.Must(newTemplate(srv.tmpl.root, "hosts.tmpl"))
+	srv.tmpl.host = template.Must(newTemplate(srv.tmpl.root, "host.tmpl"))
+
+	http.HandleFunc("/objects/services", srv.servicesHandler)
+	http.HandleFunc("/objects/services/", srv.serviceHandler)
+	http.HandleFunc("/objects/hosts", srv.hostsHandler)
+	http.HandleFunc("/objects/hosts/", srv.hostHandler)
+	http.HandleFunc("/search", srv.searchHandler)
+	http.HandleFunc("/", srv.rootHandler)
+	log.Fatal(http.ListenAndServe(":6969", nil))
+}
blob - /dev/null
blob + 3b008cb06f94d3992744c6afc8be31bf29a2df13 (mode 644)
--- /dev/null
+++ cmd/checkweb/host.tmpl
@@ -0,0 +1,27 @@
+{{define "content"}}
+<h1>
+	{{.DisplayName}}
+	<sub-title>{{.Name}}</sub-title>
+</h1>
+
+<p>{{.Notes}}
+{{if .NotesURL}}<a href="{{.NotesURL}}">More info</a>{{end}}
+</p>
+
+<table>
+	<tr><th>Last check</th><td><time>{{.LastCheck}}</time></td></tr>
+	<tr>
+		<th>State</th>
+		<td>
+			<chip class="{{if eq .State 0}}ok{{else}}bad{{end}}">{{.State}}</chip>
+			{{if .Acknowledgement}}<chip class="info">Acknowledged</chip>{{end}}
+		</td>
+	</tr>
+	<tr><th>CheckCommand</th><td>{{.CheckCommand}}</td></tr>
+	<tr><th>Raw command</th><td><code>{{.LastCheckResult.RawCommand}}</code></td></tr>
+</table>
+
+<figure>
+	<pre>{{.LastCheckResult.Output}}</pre>
+</figure>
+{{end}}
\ No newline at end of file
blob - /dev/null
blob + a43e5ac905d6484361b1c30eab9d6aa843944c04 (mode 644)
--- /dev/null
+++ cmd/checkweb/hosts.tmpl
@@ -0,0 +1,10 @@
+{{define "title"}}Hosts{{end}}
+
+{{define "content"}}
+{{range .}}
+	<div class="f-row justify-content:space-between">
+		<a href="/objects/hosts/{{.Name}}">{{.DisplayName}} ({{.Name}})</a>
+		<chip class="{{if eq .State 0}}ok{{else}}bad{{end}}">{{.State}}</chip>
+	</div>
+{{end}}
+{{end}}
\ No newline at end of file
blob - /dev/null
blob + 9b44c1371627eb15c292e4ca330d0e4782cd8278 (mode 644)
--- /dev/null
+++ cmd/checkweb/service.tmpl
@@ -0,0 +1,25 @@
+{{define "title"}}{{.Name}}{{end}}
+
+{{define "content"}}
+<h1>
+	{{.DisplayName}}
+	<sub-title>{{.Name}}</sub-title>
+</h1>
+<p>{{.Notes}}</p>
+<table>
+	<tr><th>Last check</th><td><time>{{.LastCheck}}</time></td></tr>
+	<tr>
+		<th>State</th>
+		<td>
+			<chip class="{{if eq .State 0}}ok{{else if eq .State 1}}warn{{else if eq .State 2}}bad{{else}}unknown{{end}}">{{.State}}</chip>
+			{{if .Acknowledgement}}<chip class="info">Acknowledged</chip>{{end}}
+		</td>
+	</tr>
+	<tr><th>CheckCommand</th><td>{{.CheckCommand}}</td></tr>
+	<tr><th>Raw command</th><td><code>{{.LastCheckResult.RawCommand}}</code></td></tr>
+</table>
+
+<figure>
+	<pre>{{.LastCheckResult.Output}}</pre>
+</figure>
+{{end}}
\ No newline at end of file
blob - /dev/null
blob + fd955a512b993ae7acbeb46f745f15f0074f7ef5 (mode 644)
--- /dev/null
+++ cmd/checkweb/services.tmpl
@@ -0,0 +1,10 @@
+{{define "title"}}Services{{end}}
+
+{{define "content"}}
+{{range .}}
+	<div class="f-row justify-content:space-between">
+		<a href="/objects/services/{{pathescape .Name}}">{{.DisplayName}} ({{.Name}})</a>
+		<chip class="{{if eq .State 0}}ok{{else if eq .State 1}}warn{{else if eq .State 2}}bad{{else}}unknown{{end}}">{{.State}}</chip>
+	</div>
+{{end}}
+{{end}}
\ No newline at end of file