diff --git a/server/server.go b/server/server.go
index 309cdb6b35f86541dc528b6f6df742cf30d51f39..fb5ba225a194dc3f5c94cebe6010994047961ae4 100644 (file)
--- a/server/server.go
+++ b/server/server.go
import (
"bytes"
- "errors"
"fmt"
"html/template"
"io"
+ "log"
"net/http"
"net/url"
"path/filepath"
// A Config specifies configuration values for a SysDB web server.
type Config struct {
- // Conns is a slice of connections to a SysDB server instance. The number of
- // elements specifies the maximum number of parallel queries to the backend.
- // Note that a client connection is not thread-safe but multiple idle
- // connections don't impose any load on the server.
- Conns []*client.Conn
-
// TemplatePath specifies the relative or absolute location of template files.
TemplatePath string
// StaticPath specifies the relative or absolute location of static files.
StaticPath string
+
+ // Root mount point of the server.
+ Root string
}
// A Server implements an http.Handler that serves the SysDB user interface.
type Server struct {
- conns chan *client.Conn
+ c *client.Client
// Request multiplexer
mux map[string]handler
// Base directory of static files.
basedir string
+
+ // Root mount point.
+ root string
}
// New constructs a new SysDB web server using the specified configuration.
-func New(cfg Config) (*Server, error) {
- if len(cfg.Conns) == 0 {
- return nil, errors.New("need at least one client connection")
- }
-
+func New(addr, user string, cfg Config) (*Server, error) {
s := &Server{
- conns: make(chan *client.Conn, len(cfg.Conns)),
results: make(map[string]*template.Template),
+ basedir: cfg.StaticPath,
+ root: cfg.Root,
}
- for _, c := range cfg.Conns {
- s.conns <- c
+ if s.root == "" {
+ s.root = "/"
}
var err error
- s.main, err = cfg.parse("main.tmpl")
- if err != nil {
+ if s.c, err = client.Connect(addr, user); err != nil {
return nil, err
}
+ if major, minor, patch, extra, err := s.c.ServerVersion(); err == nil {
+ log.Printf("Connected to SysDB %d.%d.%d%s.", major, minor, patch, extra)
+ }
- types := []string{"host", "hosts", "service", "services", "metric", "metrics"}
+ if s.main, err = cfg.parse(s, "main.tmpl"); err != nil {
+ return nil, err
+ }
+ types := []string{"graphs", "host", "hosts", "service", "services", "metric", "metrics"}
for _, t := range types {
- s.results[t], err = cfg.parse(t + ".tmpl")
+ s.results[t], err = cfg.parse(s, t+".tmpl")
if err != nil {
return nil, err
}
}
- s.basedir = cfg.StaticPath
s.mux = map[string]handler{
"images": s.static,
"style": s.static,
return s, nil
}
-func (cfg Config) parse(name string) (*template.Template, error) {
- t := template.New(filepath.Base(name))
+func (cfg Config) parse(s *Server, name string) (*template.Template, error) {
+ t := template.New(filepath.Base(name)).Funcs(template.FuncMap{
+ "root": s.Root,
+ })
return t.ParseFiles(filepath.Join(cfg.TemplatePath, name))
}
"": index,
// Queries
+ "graphs": graphs,
"host": fetch,
"service": fetch,
"metric": fetch,
// the SysDB user interface.
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
path := r.RequestURI
+ if !strings.HasPrefix(path, s.root) {
+ s.notfound(w, r)
+ return
+ }
+ path = strings.TrimPrefix(path, s.root)
+ r.URL.Path = strings.TrimPrefix(r.URL.Path, s.root)
+
if len(path) > 0 && path[0] == '/' {
path = path[1:]
}
+ if idx := strings.Index(path, "?"); idx != -1 {
+ path = path[:idx]
+ }
var fields []string
for _, f := range strings.Split(path, "/") {
f, err := url.QueryUnescape(f)
if err != nil {
- s.err(w, http.StatusBadRequest, fmt.Errorf("Error: %v", err))
+ s.badrequest(w, fmt.Errorf("Error: %v", err))
return
}
fields = append(fields, f)
return
}
r.ParseForm()
- page, err := f(req, s)
+ p, err := f(req, s)
if err != nil {
- s.err(w, http.StatusBadRequest, fmt.Errorf("Error: %v", err))
- return
+ p = &page{
+ Content: "<section class=\"error\">" +
+ html(fmt.Sprintf("Error: %v", err)) +
+ "</section>",
+ }
}
- page.Query = r.FormValue("query")
- if page.Title == "" {
- page.Title = "SysDB - The System Database"
+ p.Query = r.FormValue("query")
+ if p.Title == "" {
+ p.Title = "SysDB - The System Database"
}
var buf bytes.Buffer
- err = s.main.Execute(&buf, page)
+ err = s.main.Execute(&buf, p)
if err != nil {
s.internal(w, err)
return
http.ServeFile(w, req.r, filepath.Clean(filepath.Join(s.basedir, req.r.URL.Path)))
}
+// Root returns the root mount point of the server suitable for use as a path
+// prefix.
+func (s *Server) Root() string {
+ if s.root[len(s.root)-1] == '/' {
+ return s.root
+ }
+ return s.root + "/"
+}
+
func index(_ request, s *Server) (*page, error) {
- return &page{Content: "<section><h1>Welcome to the System Database.</h1></section>"}, nil
+ major, minor, patch, extra, err := s.c.ServerVersion()
+ if err != nil {
+ return nil, err
+ }
+
+ content := fmt.Sprintf("<section>"+
+ "<h1>Welcome to the System Database.</h1>"+
+ "<p>Connected to SysDB %d.%d.%d%s</p>"+
+ "</section>", major, minor, patch, html(extra))
+ return &page{Content: template.HTML(content)}, nil
}
func tmpl(t *template.Template, data interface{}) (*page, error) {