Make all paths absolute to support proxies

This commit is contained in:
Ceesjan Luiten 2015-03-24 22:04:38 +01:00
parent b456240c46
commit 0e18784c64
19 changed files with 142 additions and 93 deletions

View file

@ -12,12 +12,12 @@
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">Prometheus</a>
<a class="navbar-brand" href="{{ pathPrefix }}">Prometheus</a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a href="/alerts">Alerts</a></li>
<li><a href="{{ pathPrefix }}alerts">Alerts</a></li>
<li><a href="https://www.pagerduty.com/">PagerDuty</a></li>
</div>
</ul>

View file

@ -1,15 +1,17 @@
{{/* vim: set ft=html: */}}
{{/* Load Prometheus console library JS/CSS. Should go in <head> */}}
{{ define "prom_console_head" }}
<link type="text/css" rel="stylesheet" href="/static/vendor/rickshaw/rickshaw.min.css">
<link type="text/css" rel="stylesheet" href="/static/vendor/bootstrap-3.3.1/css/bootstrap.min.css">
<link type="text/css" rel="stylesheet" href="/static/css/prom_console.css">
<script src="/static/vendor/rickshaw/vendor/d3.v3.js"></script>
<script src="/static/vendor/rickshaw/vendor/d3.layout.min.js"></script>
<script src="/static/vendor/rickshaw/rickshaw.min.js"></script>
<script src="/static/vendor/js/jquery.min.js"></script>
<script src="/static/vendor/bootstrap-3.3.1/js/bootstrap.min.js"></script>
<script src="/static/js/prom_console.js"></script>
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}static/vendor/rickshaw/rickshaw.min.css">
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}static/vendor/bootstrap-3.3.1/css/bootstrap.min.css">
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}static/css/prom_console.css">
<script src="{{ pathPrefix }}static/vendor/rickshaw/vendor/d3.v3.js"></script>
<script src="{{ pathPrefix }}static/vendor/rickshaw/vendor/d3.layout.min.js"></script>
<script src="{{ pathPrefix }}static/vendor/rickshaw/rickshaw.min.js"></script>
<script src="{{ pathPrefix }}static/vendor/js/jquery.min.js"></script>
<script src="{{ pathPrefix }}static/vendor/bootstrap-3.3.1/js/bootstrap.min.js"></script>
<script>var PATH_PREFIX = "{{ pathPrefix }}";</script>
<script src="{{ pathPrefix }}static/js/prom_console.js"></script>
{{ end }}
{{/* Top of all pages. */}}

24
main.go
View file

@ -20,6 +20,7 @@ import (
"os/signal"
"sync"
"syscall"
"strings"
"time"
"github.com/golang/glog"
@ -65,6 +66,8 @@ var (
storageDirty = flag.Bool("storage.local.dirty", false, "If set, the local storage layer will perform crash recovery even if the last shutdown appears to be clean.")
storagePedanticChecks = flag.Bool("storage.local.pedantic-checks", false, "If set, a crash recovery will perform checks on each series file. This might take a very long time.")
pathPrefix = flag.String("web.path-prefix", "/", "Prefix for all web paths.")
printVersion = flag.Bool("version", false, "Print version information.")
)
@ -140,7 +143,8 @@ func NewPrometheus() *prometheus {
NotificationHandler: notificationHandler,
EvaluationInterval: conf.EvaluationInterval(),
Storage: memStorage,
PrometheusURL: web.MustBuildServerURL(),
PrometheusURL: web.MustBuildServerURL(*pathPrefix),
PathPrefix: *pathPrefix,
})
if err := ruleManager.AddRulesFromConfig(conf); err != nil {
glog.Fatal("Error loading rule files: ", err)
@ -157,14 +161,21 @@ func NewPrometheus() *prometheus {
TargetPools: targetManager.Pools(),
Flags: flags,
Birth: time.Now(),
PathPrefix: *pathPrefix,
}
alertsHandler := &web.AlertsHandler{
RuleManager: ruleManager,
PathPrefix: *pathPrefix,
}
consolesHandler := &web.ConsolesHandler{
Storage: memStorage,
PathPrefix: *pathPrefix,
}
graphsHandler := &web.GraphsHandler{
PathPrefix: *pathPrefix,
}
metricsService := &api.MetricsService{
@ -177,6 +188,7 @@ func NewPrometheus() *prometheus {
MetricsHandler: metricsService,
ConsolesHandler: consolesHandler,
AlertsHandler: alertsHandler,
GraphsHandler: graphsHandler,
}
p := &prometheus{
@ -205,7 +217,7 @@ func (p *prometheus) Serve() {
p.storage.Start()
go func() {
err := p.webService.ServeForever()
err := p.webService.ServeForever(*pathPrefix)
if err != nil {
glog.Fatal(err)
}
@ -255,6 +267,14 @@ func (p *prometheus) Collect(ch chan<- registry.Metric) {
func main() {
flag.Parse()
if !strings.HasPrefix(*pathPrefix, "/") {
*pathPrefix = "/" + *pathPrefix
}
if !strings.HasSuffix(*pathPrefix, "/") {
*pathPrefix = *pathPrefix + "/"
}
versionInfoTmpl.Execute(os.Stdout, BuildInfo)
if *printVersion {

View file

@ -99,6 +99,7 @@ type ruleManager struct {
notificationHandler *notification.NotificationHandler
prometheusURL string
pathPrefix string
}
// RuleManagerOptions bundles options for the RuleManager.
@ -110,6 +111,7 @@ type RuleManagerOptions struct {
SampleAppender storage.SampleAppender
PrometheusURL string
PathPrefix string
}
// NewRuleManager returns an implementation of RuleManager, ready to be started
@ -190,7 +192,7 @@ func (m *ruleManager) queueAlertNotifications(rule *rules.AlertingRule, timestam
defs := "{{$labels := .Labels}}{{$value := .Value}}"
expand := func(text string) string {
template := templates.NewTemplateExpander(defs+text, "__alert_"+rule.Name(), tmplData, timestamp, m.storage)
template := templates.NewTemplateExpander(defs+text, "__alert_"+rule.Name(), tmplData, timestamp, m.storage, m.pathPrefix)
result, err := template.Expand()
if err != nil {
result = err.Error()

View file

@ -92,7 +92,7 @@ type templateExpander struct {
}
// NewTemplateExpander returns a template expander ready to use.
func NewTemplateExpander(text string, name string, data interface{}, timestamp clientmodel.Timestamp, storage local.Storage) *templateExpander {
func NewTemplateExpander(text string, name string, data interface{}, timestamp clientmodel.Timestamp, storage local.Storage, pathPrefix string) *templateExpander {
return &templateExpander{
text: text,
name: name,
@ -218,6 +218,9 @@ func NewTemplateExpander(text string, name string, data interface{}, timestamp c
}
return fmt.Sprintf("%.4g%ss", v, prefix)
},
"pathPrefix": func() string {
return pathPrefix;
},
},
}
}

View file

@ -178,7 +178,7 @@ func TestTemplateExpansion(t *testing.T) {
for i, s := range scenarios {
var result string
var err error
expander := NewTemplateExpander(s.text, "test", s.input, time, storage)
expander := NewTemplateExpander(s.text, "test", s.input, time, storage, "/")
if s.html {
result, err = expander.ExpandHTML(nil)
} else {

View file

@ -47,9 +47,9 @@ func (s byAlertStateSorter) Swap(i, j int) {
// AlertsHandler implements http.Handler.
type AlertsHandler struct {
mutex sync.Mutex
RuleManager manager.RuleManager
mutex sync.Mutex
PathPrefix string
}
func (h *AlertsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
@ -68,5 +68,5 @@ func (h *AlertsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
rules.Firing: "danger",
},
}
executeTemplate(w, "alerts", alertStatus)
executeTemplate(w, "alerts", alertStatus, h.PathPrefix)
}

View file

@ -31,19 +31,19 @@ type MetricsService struct {
}
// RegisterHandler registers the handler for the various endpoints below /api.
func (msrv *MetricsService) RegisterHandler() {
func (msrv *MetricsService) RegisterHandler(pathPrefix string) {
handler := func(h func(http.ResponseWriter, *http.Request)) http.Handler {
return httputils.CompressionHandler{
Handler: http.HandlerFunc(h),
}
}
http.Handle("/api/query", prometheus.InstrumentHandler(
"/api/query", handler(msrv.Query),
http.Handle(pathPrefix + "api/query", prometheus.InstrumentHandler(
pathPrefix + "api/query", handler(msrv.Query),
))
http.Handle("/api/query_range", prometheus.InstrumentHandler(
"/api/query_range", handler(msrv.QueryRange),
http.Handle(pathPrefix + "api/query_range", prometheus.InstrumentHandler(
pathPrefix + "api/query_range", handler(msrv.QueryRange),
))
http.Handle("/api/metrics", prometheus.InstrumentHandler(
"/api/metrics", handler(msrv.Metrics),
http.Handle(pathPrefix + "api/metrics", prometheus.InstrumentHandler(
pathPrefix + "api/metrics", handler(msrv.Metrics),
))
}

View file

@ -95,7 +95,7 @@ func TestQuery(t *testing.T) {
Now: testNow,
Storage: storage,
}
api.RegisterHandler()
api.RegisterHandler("/")
server := httptest.NewServer(http.DefaultServeMux)
defer server.Close()

View file

@ -34,6 +34,7 @@ var (
// ConsolesHandler implements http.Handler.
type ConsolesHandler struct {
Storage local.Storage
PathPrefix string
}
func (h *ConsolesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
@ -69,7 +70,7 @@ func (h *ConsolesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
Path: r.URL.Path,
}
template := templates.NewTemplateExpander(string(text), "__console_"+r.URL.Path, data, clientmodel.Now(), h.Storage)
template := templates.NewTemplateExpander(string(text), "__console_"+r.URL.Path, data, clientmodel.Now(), h.Storage, h.PathPrefix)
filenames, err := filepath.Glob(*consoleLibrariesPath + "/*.lib")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)

View file

@ -17,6 +17,11 @@ import (
"net/http"
)
func graphHandler(w http.ResponseWriter, r *http.Request) {
executeTemplate(w, "graph", nil)
// GraphsHandler implements http.Handler.
type GraphsHandler struct {
PathPrefix string
}
func (h *GraphsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
executeTemplate(w, "graph", nil, h.PathPrefix)
}

View file

@ -4,6 +4,8 @@ var graphTemplate;
var SECOND = 1000;
Handlebars.registerHelper('pathPrefix', function() { return PATH_PREFIX; });
Prometheus.Graph = function(element, options) {
this.el = element;
this.options = options;
@ -158,7 +160,7 @@ Prometheus.Graph.prototype.populateInsertableMetrics = function() {
var self = this;
$.ajax({
method: "GET",
url: "/api/metrics",
url: PATH_PREFIX + "api/metrics",
dataType: "json",
success: function(json, textStatus) {
var availableMetrics = [];
@ -310,7 +312,7 @@ Prometheus.Graph.prototype.submitQuery = function() {
url = self.queryForm.attr("action");
success = function(json, textStatus) { self.handleGraphResponse(json, textStatus); };
} else {
url = "/api/query";
url = PATH_PREFIX + "api/query";
success = function(text, textStatus) { self.handleConsoleResponse(text, textStatus); };
}
@ -609,7 +611,7 @@ function init() {
});
$.ajax({
url: "/static/js/graph_template.handlebar",
url: PATH_PREFIX + "static/js/graph_template.handlebar",
success: function(data) {
graphTemplate = Handlebars.compile(data);
var options = parseGraphOptionsFromURL();

View file

@ -1,5 +1,5 @@
<div id="graph_wrapper{{id}}" class="graph_wrapper">
<form action="/api/query_range" method="GET" class="query_form form-inline">
<form action="{{ pathPrefix }}api/query_range" method="GET" class="query_form form-inline">
<div class="row">
<div class="col-lg-10">
<span class="input-group expression_input_group">
@ -14,7 +14,7 @@
</div>
<div class="col-lg-2">
<div class="eval_stats pull-right"></div>
<img src="/static/img/ajax-loader.gif" class="spinner" alt="ajax_spinner">
<img src="{{ pathPrefix }}static/img/ajax-loader.gif" class="spinner" alt="ajax_spinner">
</div>
</div>
<div class="row">

View file

@ -502,7 +502,7 @@ PromConsole.Graph.prototype.dispatch = function() {
var pending_requests = this.params.expr.length;
for (var i = 0; i < this.params.expr.length; ++i) {
var endTime = this.params.endTime;
var url = "/api/query_range?expr=" + encodeURIComponent(this.params.expr[i])
var url = PATH_PREFIX + "api/query_range?expr=" + encodeURIComponent(this.params.expr[i])
+ "&step=" + this.params.duration / this.graphTd.offsetWidth
+ "&range=" + this.params.duration + "&end=" + endTime;
var xhr = new XMLHttpRequest();
@ -539,7 +539,7 @@ PromConsole.Graph.prototype.dispatch = function() {
}
var loadingImg = document.createElement("img");
loadingImg.src = '/static/img/ajax-loader.gif';
loadingImg.src = PATH_PREFIX + 'static/img/ajax-loader.gif';
loadingImg.alt = 'Loading...';
loadingImg.className = 'prom_graph_loading';
this.graphTd.appendChild(loadingImg);
@ -605,6 +605,6 @@ PromConsole._graphsToSlashGraphURL = function(exprs) {
for (var i = 0; i < exprs.length; ++i) {
data.push({'expr': exprs[i], 'tab': 0});
}
return '/graph#' + encodeURIComponent(JSON.stringify(data));
return PATH_PREFIX + 'graph#' + encodeURIComponent(JSON.stringify(data));
};

View file

@ -33,6 +33,7 @@ type PrometheusStatusHandler struct {
TargetPools map[string]*retrieval.TargetPool
Birth time.Time
PathPrefix string
}
// TargetStateToClass returns a map of TargetState to the name of a Bootstrap CSS class.
@ -45,5 +46,5 @@ func (h *PrometheusStatusHandler) TargetStateToClass() map[retrieval.TargetState
}
func (h *PrometheusStatusHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
executeTemplate(w, "status", h)
executeTemplate(w, "status", h, h.PathPrefix)
}

View file

@ -3,10 +3,12 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Prometheus Time Series Collection and Processing Server</title>
<script src="/static/vendor/js/jquery.min.js"></script>
<script src="{{ pathPrefix }}static/vendor/js/jquery.min.js"></script>
<link type="text/css" rel="stylesheet" href="/static/vendor/bootstrap-3.3.1/css/bootstrap.min.css">
<link type="text/css" rel="stylesheet" href="/static/css/prometheus.css">
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}static/vendor/bootstrap-3.3.1/css/bootstrap.min.css">
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}static/css/prometheus.css">
<script>var PATH_PREFIX = "{{ pathPrefix }}";</script>
{{template "head" .}}
</head>
@ -21,17 +23,17 @@
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">Prometheus</a>
<a class="navbar-brand" href="{{ pathPrefix }}">Prometheus</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-left">
{{$consoles := getConsoles}}
{{if $consoles}}
<li><a href="{{$consoles}}">Consoles</a></li>
<li><a href="{{ pathPrefix }}{{$consoles}}">Consoles</a></li>
{{ end }}
<li><a href="/alerts">Alerts</a></li>
<li><a href="/graph">Graph</a></li>
<li><a href="/">Status</a></li>
<li><a href="{{ pathPrefix }}alerts">Alerts</a></li>
<li><a href="{{ pathPrefix }}graph">Graph</a></li>
<li><a href="{{ pathPrefix }}">Status</a></li>
<li>
<a href="http://prometheus.io" target="_blank">Help</a>
</li>

View file

@ -1,6 +1,6 @@
{{define "head"}}
<link type="text/css" rel="stylesheet" href="/static/css/alerts.css">
<script src="/static/js/alerts.js"></script>
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}static/css/alerts.css">
<script src="{{ pathPrefix }}static/js/alerts.js"></script>
{{end}}
{{define "content"}}

View file

@ -1,22 +1,22 @@
{{define "head"}}
<script src="/static/vendor/bootstrap-3.3.1/js/bootstrap.min.js"></script>
<script src="{{ pathPrefix }}static/vendor/bootstrap-3.3.1/js/bootstrap.min.js"></script>
<link type="text/css" rel="stylesheet" href="/static/css/graph.css">
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}static/css/graph.css">
<link type="text/css" rel="stylesheet" href="/static/vendor/rickshaw/rickshaw.min.css">
<link type="text/css" rel="stylesheet" href="/static/vendor/bootstrap-datetimepicker/bootstrap-datetimepicker.min.css">
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}static/vendor/rickshaw/rickshaw.min.css">
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}static/vendor/bootstrap-datetimepicker/bootstrap-datetimepicker.min.css">
<script src="/static/vendor/rickshaw/vendor/d3.v3.js"></script>
<script src="/static/vendor/rickshaw/vendor/d3.layout.min.js"></script>
<script src="/static/vendor/rickshaw/rickshaw.min.js"></script>
<script src="/static/vendor/bootstrap-datetimepicker/bootstrap-datetimepicker.js"></script>
<script src="/static/vendor/bootstrap3-typeahead/bootstrap3-typeahead.min.js"></script>
<script src="{{ pathPrefix }}static/vendor/rickshaw/vendor/d3.v3.js"></script>
<script src="{{ pathPrefix }}static/vendor/rickshaw/vendor/d3.layout.min.js"></script>
<script src="{{ pathPrefix }}static/vendor/rickshaw/rickshaw.min.js"></script>
<script src="{{ pathPrefix }}static/vendor/bootstrap-datetimepicker/bootstrap-datetimepicker.js"></script>
<script src="{{ pathPrefix }}static/vendor/bootstrap3-typeahead/bootstrap3-typeahead.min.js"></script>
<script src="/static/vendor/js/handlebars.js"></script>
<script src="/static/vendor/js/jquery.selection.js"></script>
<script src="/static/vendor/js/jquery.hotkeys.js"></script>
<script src="{{ pathPrefix }}static/vendor/js/handlebars.js"></script>
<script src="{{ pathPrefix }}static/vendor/js/jquery.selection.js"></script>
<script src="{{ pathPrefix }}static/vendor/js/jquery.hotkeys.js"></script>
<script src="/static/js/graph.js"></script>
<script src="{{ pathPrefix }}static/js/graph.js"></script>
<script id="graph_template" type="text/x-handlebars-template"></script>
{{end}}

View file

@ -21,6 +21,7 @@ import (
"net"
"net/http"
"os"
"strings"
"time"
pprof_runtime "runtime/pprof"
@ -47,52 +48,60 @@ type WebService struct {
MetricsHandler *api.MetricsService
AlertsHandler *AlertsHandler
ConsolesHandler *ConsolesHandler
GraphsHandler *GraphsHandler
QuitChan chan struct{}
}
// ServeForever serves the HTTP endpoints and only returns upon errors.
func (ws WebService) ServeForever() error {
func (ws WebService) ServeForever(pathPrefix string) error {
http.Handle("/favicon.ico", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "", 404)
}))
http.Handle("/", prometheus.InstrumentHandler(
"/", ws.StatusHandler,
http.Handle(pathPrefix, prometheus.InstrumentHandler(
pathPrefix, ws.StatusHandler,
))
http.Handle("/alerts", prometheus.InstrumentHandler(
"/alerts", ws.AlertsHandler,
http.Handle(pathPrefix + "alerts", prometheus.InstrumentHandler(
pathPrefix + "alerts", ws.AlertsHandler,
))
http.Handle("/consoles/", prometheus.InstrumentHandler(
"/consoles/", http.StripPrefix("/consoles/", ws.ConsolesHandler),
http.Handle(pathPrefix + "consoles/", prometheus.InstrumentHandler(
pathPrefix + "consoles/", http.StripPrefix(pathPrefix + "consoles/", ws.ConsolesHandler),
))
http.Handle("/graph", prometheus.InstrumentHandler(
"/graph", http.HandlerFunc(graphHandler),
http.Handle(pathPrefix + "graph", prometheus.InstrumentHandler(
pathPrefix + "graph", ws.GraphsHandler,
))
http.Handle("/heap", prometheus.InstrumentHandler(
"/heap", http.HandlerFunc(dumpHeap),
http.Handle(pathPrefix + "heap", prometheus.InstrumentHandler(
pathPrefix + "heap", http.HandlerFunc(dumpHeap),
))
ws.MetricsHandler.RegisterHandler()
http.Handle(*metricsPath, prometheus.Handler())
ws.MetricsHandler.RegisterHandler(pathPrefix)
http.Handle(pathPrefix + strings.TrimLeft(*metricsPath, "/"), prometheus.Handler())
if *useLocalAssets {
http.Handle("/static/", prometheus.InstrumentHandler(
"/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("web/static"))),
http.Handle(pathPrefix + "static/", prometheus.InstrumentHandler(
pathPrefix + "static/", http.StripPrefix(pathPrefix + "static/", http.FileServer(http.Dir("web/static"))),
))
} else {
http.Handle("/static/", prometheus.InstrumentHandler(
"/static/", http.StripPrefix("/static/", new(blob.Handler)),
http.Handle(pathPrefix + "static/", prometheus.InstrumentHandler(
pathPrefix + "static/", http.StripPrefix(pathPrefix + "static/", new(blob.Handler)),
))
}
if *userAssetsPath != "" {
http.Handle("/user/", prometheus.InstrumentHandler(
"/user/", http.StripPrefix("/user/", http.FileServer(http.Dir(*userAssetsPath))),
http.Handle(pathPrefix + "user/", prometheus.InstrumentHandler(
pathPrefix + "user/", http.StripPrefix(pathPrefix + "user/", http.FileServer(http.Dir(*userAssetsPath))),
))
}
if *enableQuit {
http.Handle("/-/quit", http.HandlerFunc(ws.quitHandler))
http.Handle(pathPrefix + "-/quit", http.HandlerFunc(ws.quitHandler))
}
if pathPrefix != "/" {
http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, pathPrefix, http.StatusFound)
}))
}
glog.Info("listening on ", *listenAddress)
@ -129,23 +138,25 @@ func getTemplateFile(name string) (string, error) {
return string(file), nil
}
func getConsoles() string {
if _, err := os.Stat(*consoleTemplatesPath + "/index.html"); !os.IsNotExist(err) {
return "/consoles/index.html"
func getConsoles(pathPrefix string) string {
if _, err := os.Stat(pathPrefix + *consoleTemplatesPath + "/index.html"); !os.IsNotExist(err) {
return pathPrefix + "consoles/index.html"
}
if *userAssetsPath != "" {
if _, err := os.Stat(*userAssetsPath + "/index.html"); !os.IsNotExist(err) {
return "/user/index.html"
if _, err := os.Stat(pathPrefix + *userAssetsPath + "/index.html"); !os.IsNotExist(err) {
return pathPrefix + "user/index.html"
}
}
return ""
}
func getTemplate(name string) (t *template.Template, err error) {
func getTemplate(name string, pathPrefix string) (t *template.Template, err error) {
t = template.New("_base")
t.Funcs(template.FuncMap{
"since": time.Since,
"getConsoles": getConsoles,
"getConsoles": func() string { return getConsoles(pathPrefix) },
"pathPrefix": func() string { return pathPrefix },
})
file, err := getTemplateFile("_base")
if err != nil {
@ -163,8 +174,8 @@ func getTemplate(name string) (t *template.Template, err error) {
return
}
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
@ -188,7 +199,7 @@ func dumpHeap(w http.ResponseWriter, r *http.Request) {
}
// MustBuildServerURL returns the server URL and panics in case an error occurs.
func MustBuildServerURL() string {
func MustBuildServerURL(pathPrefix string) string {
_, port, err := net.SplitHostPort(*listenAddress)
if err != nil {
panic(err)
@ -197,5 +208,5 @@ func MustBuildServerURL() string {
if err != nil {
panic(err)
}
return fmt.Sprintf("http://%s:%s", hostname, port)
return fmt.Sprintf("http://%s:%s%s", hostname, port, pathPrefix)
}