diff --git a/main.go b/main.go index 8a1861fd..e2b6348d 100644 --- a/main.go +++ b/main.go @@ -16,6 +16,7 @@ package main import ( "flag" "os" + "strings" "time" "github.com/golang/glog" @@ -30,11 +31,19 @@ var ( configFile = flag.String("config.file", "alertmanager.conf", "Alert Manager configuration file name.") silencesFile = flag.String("silences.file", "silences.json", "Silence storage file name.") minRefreshPeriod = flag.Duration("alerts.min-refresh-period", 5*time.Minute, "Minimum required alert refresh period before an alert is purged.") + pathPrefix = flag.String("web.path-prefix", "/", "Prefix for all web paths.") ) func main() { flag.Parse() + if !strings.HasPrefix(*pathPrefix, "/") { + *pathPrefix = "/" + *pathPrefix + } + if !strings.HasSuffix(*pathPrefix, "/") { + *pathPrefix = *pathPrefix + "/" + } + versionInfoTmpl.Execute(os.Stdout, BuildInfo) conf := config.MustLoadFromFile(*configFile) @@ -79,17 +88,19 @@ func main() { }) statusHandler := &web.StatusHandler{ - Config: conf.String(), - Flags: flags, - BuildInfo: BuildInfo, - Birth: time.Now(), + Config: conf.String(), + Flags: flags, + BuildInfo: BuildInfo, + Birth: time.Now(), + PathPrefix: *pathPrefix, } webService := &web.WebService{ // REST API Service. AlertManagerService: &api.AlertManagerService{ - Manager: alertManager, - Silencer: silencer, + Manager: alertManager, + Silencer: silencer, + PathPrefix: *pathPrefix, }, // Template-based page handlers. @@ -102,7 +113,7 @@ func main() { }, StatusHandler: statusHandler, } - go webService.ServeForever() + go webService.ServeForever(*pathPrefix) // React to configuration changes. watcher := config.NewFileWatcher(*configFile) diff --git a/web/alerts.go b/web/alerts.go index 4990271e..049975aa 100644 --- a/web/alerts.go +++ b/web/alerts.go @@ -27,6 +27,7 @@ type AlertStatus struct { type AlertsHandler struct { Manager manager.AlertManager IsSilencedInterrogator manager.IsSilencedInterrogator + PathPrefix string } func (h *AlertsHandler) silenceForAlert(a *manager.Alert) *manager.Silence { @@ -39,5 +40,5 @@ func (h *AlertsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { AlertAggregates: h.Manager.GetAll(nil), SilenceForAlert: h.silenceForAlert, } - executeTemplate(w, "alerts", alertStatus) + executeTemplate(w, "alerts", alertStatus, h.PathPrefix) } diff --git a/web/api/api.go b/web/api/api.go index 8d82c55a..ab3e1164 100644 --- a/web/api/api.go +++ b/web/api/api.go @@ -27,17 +27,17 @@ import ( type AlertManagerService struct { Manager manager.AlertManager Silencer *manager.Silencer + PathPrefix string } func (s AlertManagerService) Handler() http.Handler { r := httprouter.New() - - r.POST("/api/alerts", s.addAlerts) - r.GET("/api/silences", s.silenceSummary) - r.POST("/api/silences", s.addSilence) - r.GET("/api/silences/:id", s.getSilence) - r.POST("/api/silences/:id", s.updateSilence) - r.DELETE("/api/silences/:id", s.deleteSilence) + r.POST(s.PathPrefix + "api/alerts", s.addAlerts) + r.GET(s.PathPrefix + "api/silences", s.silenceSummary) + r.POST(s.PathPrefix + "api/silences", s.addSilence) + r.GET(s.PathPrefix + "api/silences/:id", s.getSilence) + r.POST(s.PathPrefix + "api/silences/:id", s.updateSilence) + r.DELETE(s.PathPrefix + "api/silences/:id", s.deleteSilence) return r } diff --git a/web/silences.go b/web/silences.go index 711732cf..7387e509 100644 --- a/web/silences.go +++ b/web/silences.go @@ -25,11 +25,12 @@ type SilenceStatus struct { type SilencesHandler struct { Silencer *manager.Silencer + PathPrefix string } func (h *SilencesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { silenceStatus := &SilenceStatus{ Silences: h.Silencer.SilenceSummary(), } - executeTemplate(w, "silences", silenceStatus) + executeTemplate(w, "silences", silenceStatus, h.PathPrefix) } diff --git a/web/status.go b/web/status.go index 8c939a32..8336e318 100644 --- a/web/status.go +++ b/web/status.go @@ -22,10 +22,11 @@ import ( type StatusHandler struct { mu sync.Mutex - BuildInfo map[string]string - Config string - Flags map[string]string - Birth time.Time + BuildInfo map[string]string + Config string + Flags map[string]string + Birth time.Time + PathPrefix string } func (h *StatusHandler) UpdateConfig(c string) { @@ -39,5 +40,5 @@ func (h *StatusHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { h.mu.Lock() defer h.mu.Unlock() - executeTemplate(w, "status", h) + executeTemplate(w, "status", h, h.PathPrefix) } diff --git a/web/templates/_base.html b/web/templates/_base.html index 37dfcba1..cb50a8e2 100644 --- a/web/templates/_base.html +++ b/web/templates/_base.html @@ -7,13 +7,13 @@ - - + + - - + + - + {{template "head" .}} @@ -26,9 +26,9 @@ {{define "alertsTabClass"}}{{end}} {{define "silencesTabClass"}}{{end}} {{define "statusTabClass"}}{{end}} -
  • Alerts
  • -
  • Silences
  • -
  • Status
  • +
  • Alerts
  • +
  • Silences
  • +
  • Status
  • diff --git a/web/templates/alerts.html b/web/templates/alerts.html index 18714f0d..4d5d17e3 100644 --- a/web/templates/alerts.html +++ b/web/templates/alerts.html @@ -1,7 +1,7 @@ {{define "alertsTabClass"}}active{{end}} {{define "head"}} - + {{end}} {{define "content"}} diff --git a/web/templates/silences.html b/web/templates/silences.html index bc0d2279..1faed235 100644 --- a/web/templates/silences.html +++ b/web/templates/silences.html @@ -1,7 +1,7 @@ {{define "silencesTabClass"}}active{{end}} {{define "head"}} - + {{end}} {{define "content"}} diff --git a/web/web.go b/web/web.go index 62a0fad4..3b0c59ea 100644 --- a/web/web.go +++ b/web/web.go @@ -40,51 +40,58 @@ type WebService struct { StatusHandler *StatusHandler } -func (w WebService) ServeForever() error { +func (w WebService) ServeForever(pathPrefix string) error { - http.Handle("/favicon.ico", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Handle(pathPrefix + "favicon.ico", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Error(w, "", 404) })) + http.HandleFunc("/", prometheus.InstrumentHandlerFunc("index", func(rw http.ResponseWriter, req *http.Request) { // The "/" pattern matches everything, so we need to check // that we're at the root here. - if req.URL.Path != "/" { + if req.URL.Path == pathPrefix { + w.AlertsHandler.ServeHTTP(rw, req) + } else if req.URL.Path == "/" { + // We're running under a prefix but the user requested "/". + http.Redirect(rw, req, pathPrefix, http.StatusFound) + } else { http.NotFound(rw, req) - return } - w.AlertsHandler.ServeHTTP(rw, req) })) - http.Handle("/alerts", prometheus.InstrumentHandler("alerts", w.AlertsHandler)) - http.Handle("/silences", prometheus.InstrumentHandler("silences", w.SilencesHandler)) - http.Handle("/status", prometheus.InstrumentHandler("status", w.StatusHandler)) + http.Handle(pathPrefix + "alerts", prometheus.InstrumentHandler("alerts", w.AlertsHandler)) + http.Handle(pathPrefix + "silences", prometheus.InstrumentHandler("silences", w.SilencesHandler)) + http.Handle(pathPrefix + "status", prometheus.InstrumentHandler("status", w.StatusHandler)) - http.Handle("/metrics", prometheus.Handler()) + http.Handle(pathPrefix + "metrics", prometheus.Handler()) if *useLocalAssets { - http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("web/static")))) + http.Handle(pathPrefix + "static/", http.StripPrefix(pathPrefix + "static/", http.FileServer(http.Dir("web/static")))) } else { - http.Handle("/static/", http.StripPrefix("/static/", new(blob.Handler))) + http.Handle(pathPrefix + "static/", http.StripPrefix(pathPrefix + "static/", new(blob.Handler))) } - http.Handle("/api/", w.AlertManagerService.Handler()) + http.Handle(pathPrefix + "api/", w.AlertManagerService.Handler()) glog.Info("listening on ", *listenAddress) return http.ListenAndServe(*listenAddress, nil) } -func getLocalTemplate(name string) (*template.Template, error) { +func getLocalTemplate(name string, pathPrefix string) (*template.Template, error) { t := template.New("_base.html") t.Funcs(webHelpers) + t.Funcs(template.FuncMap{"pathPrefix": func() string { return pathPrefix }}) + return t.ParseFiles( "web/templates/_base.html", fmt.Sprintf("web/templates/%s.html", name), ) } -func getEmbeddedTemplate(name string) (*template.Template, error) { +func getEmbeddedTemplate(name string, pathPrefix string) (*template.Template, error) { t := template.New("_base.html") t.Funcs(webHelpers) + t.Funcs(template.FuncMap{"pathPrefix": func() string { return pathPrefix }}) file, err := blob.GetFile(blob.TemplateFiles, "_base.html") if err != nil { @@ -103,11 +110,11 @@ func getEmbeddedTemplate(name string) (*template.Template, error) { return t, nil } -func getTemplate(name string) (t *template.Template, err error) { +func getTemplate(name string, pathPrefix string) (t *template.Template, err error) { if *useLocalAssets { - t, err = getLocalTemplate(name) + t, err = getLocalTemplate(name, pathPrefix) } else { - t, err = getEmbeddedTemplate(name) + t, err = getEmbeddedTemplate(name, pathPrefix) } if err != nil { @@ -117,8 +124,8 @@ func getTemplate(name string) (t *template.Template, err error) { return t, nil } -func executeTemplate(w http.ResponseWriter, name string, data interface{}) { - tpl, err := getTemplate(name) +func executeTemplate(w http.ResponseWriter, name string, data interface{}, pathPrefix string) { + tpl, err := getTemplate(name, pathPrefix) if err != nil { glog.Error("Error preparing layout template: ", err) return