diff --git a/cmd/goatcounter/saas.go b/cmd/goatcounter/saas.go index 5475420e..5a609204 100644 --- a/cmd/goatcounter/saas.go +++ b/cmd/goatcounter/saas.go @@ -80,10 +80,10 @@ func cmdSaas(f zli.Flags, ready chan<- struct{}, stop chan struct{}) error { hosts := map[string]http.Handler{ d: zhttp.RedirectHost("//www." + domain), "www." + d: handlers.NewWebsite(db, dev), - "*": handlers.NewBackend(db, acmeh, dev, c.GoatcounterCom, websocket, c.DomainStatic, 15, apiMax), + "*": handlers.NewBackend(db, acmeh, dev, c.GoatcounterCom, websocket, c.DomainStatic, c.BasePath, 15, apiMax), } if dev { - hosts[znet.RemovePort(domainStatic)] = handlers.NewStatic(chi.NewRouter(), dev, true) + hosts[znet.RemovePort(domainStatic)] = handlers.NewStatic(chi.NewRouter(), dev, true, c.BasePath) } return doServe(ctx, db, listen, listenTLS, tlsc, hosts, stop, func() { diff --git a/cmd/goatcounter/serve.go b/cmd/goatcounter/serve.go index 92ec7360..b22baf9d 100644 --- a/cmd/goatcounter/serve.go +++ b/cmd/goatcounter/serve.go @@ -81,6 +81,12 @@ Flags: -public-port Port your site is publicly accessible on. Only needed if it's not 80 or 443. + -base-path Path under which GoatCounter is available. Usually GoatCounter + runs on its own domain or subdomain ("stats.example.com"), but in + some cases it's useful to run GoatCounter under a path + ("example.com/stats"), in which case you'll need to set this to + "/stats". + -automigrate Automatically run all pending migrations on startup. -smtp SMTP relay server, as URL (e.g. "smtp://user:pass@server"). @@ -164,6 +170,7 @@ func cmdServe(f zli.Flags, ready chan<- struct{}, stop chan struct{}) error { var ( // TODO(depr): -port is for compat with <2.0 port = f.Int(0, "public-port", "port").Pointer() + basePath = f.String("/", "base-path").Pointer() domainStatic = f.String("", "static").Pointer() ) dbConnect, dbConn, dev, automigrate, listen, flagTLS, from, websocket, apiMax, err := flagsServe(f, &v) @@ -171,11 +178,17 @@ func cmdServe(f zli.Flags, ready chan<- struct{}, stop chan struct{}) error { return err } - return func(port int, domainStatic string) error { + return func(port int, basePath, domainStatic string) error { if flagTLS == "" { flagTLS = map[bool]string{true: "http", false: "acme,rdr"}[dev] } + basePath = strings.Trim(basePath, "/") + if basePath != "" { + basePath = "/" + basePath + } + zhttp.BasePath = basePath + var domainCount, urlStatic string if domainStatic != "" { if p := strings.Index(domainStatic, ":"); p > -1 { @@ -185,6 +198,8 @@ func cmdServe(f zli.Flags, ready chan<- struct{}, stop chan struct{}) error { } urlStatic = "//" + domainStatic domainCount = domainStatic + } else { + urlStatic = basePath } //from := flagFrom(from, "cfg.Domain", &v) @@ -206,17 +221,18 @@ func cmdServe(f zli.Flags, ready chan<- struct{}, stop chan struct{}) error { c.DomainStatic = domainStatic c.Dev = dev c.URLStatic = urlStatic + c.BasePath = basePath c.DomainCount = domainCount c.Websocket = websocket // Set up HTTP handler and servers. hosts := map[string]http.Handler{ - "*": handlers.NewBackend(db, acmeh, dev, c.GoatcounterCom, websocket, c.DomainStatic, 60, apiMax), + "*": handlers.NewBackend(db, acmeh, dev, c.GoatcounterCom, websocket, c.DomainStatic, c.BasePath, 60, apiMax), } if domainStatic != "" { // May not be needed, but just in case the DomainStatic isn't an // external CDN. - hosts[znet.RemovePort(domainStatic)] = handlers.NewStatic(chi.NewRouter(), dev, false) + hosts[znet.RemovePort(domainStatic)] = handlers.NewStatic(chi.NewRouter(), dev, false, c.BasePath) } cnames, err := lsSites(ctx) @@ -238,7 +254,7 @@ func cmdServe(f zli.Flags, ready chan<- struct{}, stop chan struct{}) error { } ready <- struct{}{} }) - }(*port, *domainStatic) + }(*port, *basePath, *domainStatic) } func doServe(ctx context.Context, db zdb.DB, diff --git a/context.go b/context.go index ec99117c..6302f299 100644 --- a/context.go +++ b/context.go @@ -42,6 +42,7 @@ type GlobalConfig struct { Domain string DomainStatic string DomainCount string + BasePath string URLStatic string Dev bool GoatcounterCom bool diff --git a/go.mod b/go.mod index 79edde75..9e7fc139 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( zgo.at/zcache v1.2.0 zgo.at/zcache/v2 v2.1.0 zgo.at/zdb v0.0.0-20240818155550-1a862f98cab0 - zgo.at/zhttp v0.0.0-20240812113805-6333261ded60 + zgo.at/zhttp v0.0.0-20240819012318-b761c83c740e zgo.at/zli v0.0.0-20240614180544-47534b1ce136 zgo.at/zlog v0.0.0-20211017235224-dd4772ddf860 zgo.at/zprof v0.0.0-20211217104121-c3c12596d8f0 diff --git a/go.sum b/go.sum index 262318ad..f37e678e 100644 --- a/go.sum +++ b/go.sum @@ -135,8 +135,8 @@ zgo.at/zcache/v2 v2.1.0 h1:USo+ubK+R4vtjw4viGzTe/zjXyPw6R7SK/RL3epBBxs= zgo.at/zcache/v2 v2.1.0/go.mod h1:gyCeoLVo01QjDZynjime8xUGHHMbsLiPyUTBpDGd4Gk= zgo.at/zdb v0.0.0-20240818155550-1a862f98cab0 h1:kqQjQGkuuU4Tx1Ltsb2TYC2wTgWen1chHe9OSSq68ig= zgo.at/zdb v0.0.0-20240818155550-1a862f98cab0/go.mod h1:hXIbV/v/ENSl5CfzICL/jpTjYb50k/gKi2kN8UeuZcY= -zgo.at/zhttp v0.0.0-20240812113805-6333261ded60 h1:lblPmTZwREdhxVd/3sSC+AuwuzpejRHwE1uCe3BBtN4= -zgo.at/zhttp v0.0.0-20240812113805-6333261ded60/go.mod h1:/cHu19ZTOpAvVTntj45jy52UORMnBJh+G8Gz6oIraCs= +zgo.at/zhttp v0.0.0-20240819012318-b761c83c740e h1:XkppPemmGbgCauiZOCUpWtvPor9yiB9611AsFUoVmho= +zgo.at/zhttp v0.0.0-20240819012318-b761c83c740e/go.mod h1:OEB7qL85qu5BBFfdmep9TTnUUb25j3aqEYYuUnmFqX4= zgo.at/zli v0.0.0-20240614180544-47534b1ce136 h1:Q0j5M5+5YGNaECQmKOcznyDYX3jZUCVN+c7GKUkoV8o= zgo.at/zli v0.0.0-20240614180544-47534b1ce136/go.mod h1:0jjx+AGEkWOOQ0NtzbMnpko+H2G+aTg8mfCKqoc/BuA= zgo.at/zlog v0.0.0-20211017235224-dd4772ddf860 h1:7n74jp98CwBdqGCqJoVv2+XygJ3yD43GPUivnj/RPwo= diff --git a/handlers/backend.go b/handlers/backend.go index 3466eae1..b17a21d1 100644 --- a/handlers/backend.go +++ b/handlers/backend.go @@ -19,8 +19,17 @@ import ( "zgo.at/zstd/zfs" ) -func NewBackend(db zdb.DB, acmeh http.HandlerFunc, dev, goatcounterCom, websocket bool, domainStatic string, dashTimeout, apiMax int) chi.Router { - r := chi.NewRouter() +func NewBackend(db zdb.DB, acmeh http.HandlerFunc, dev, goatcounterCom, websocket bool, + domainStatic string, basePath string, dashTimeout, apiMax int, +) chi.Router { + + root := chi.NewRouter() + r := root + if basePath != "" { + r = chi.NewRouter() + root.Mount(basePath, r) + } + backend{dashTimeout, websocket}.Mount(r, db, dev, domainStatic, dashTimeout, apiMax) if acmeh != nil { @@ -28,10 +37,10 @@ func NewBackend(db zdb.DB, acmeh http.HandlerFunc, dev, goatcounterCom, websocke } if !goatcounterCom { - NewStatic(r, dev, goatcounterCom) + NewStatic(r, dev, goatcounterCom, basePath) } - return r + return root } type backend struct { diff --git a/handlers/backend_test.go b/handlers/backend_test.go index b5111d66..7bf0d840 100644 --- a/handlers/backend_test.go +++ b/handlers/backend_test.go @@ -249,5 +249,5 @@ func BenchmarkCount(b *testing.B) { } func newBackend(db zdb.DB) chi.Router { - return NewBackend(db, nil, true, true, false, "example.com", 10, 0) + return NewBackend(db, nil, true, true, false, "example.com", "", 10, 0) } diff --git a/handlers/dashboard.go b/handlers/dashboard.go index 956c0f5e..950e3ad1 100644 --- a/handlers/dashboard.go +++ b/handlers/dashboard.go @@ -110,10 +110,7 @@ func (h backend) dashboard(w http.ResponseWriter, r *http.Request) error { cd := goatcounter.Config(r.Context()).DomainCount if cd == "" { - cd = Site(r.Context()).Domain(r.Context()) - if goatcounter.Config(r.Context()).Port != "" { - cd += ":" + goatcounter.Config(r.Context()).Port - } + cd = Site(r.Context()).SchemelessURL(r.Context()) } args := widgets.Args{ diff --git a/handlers/handlers.go b/handlers/handlers.go index fb9ee4ac..6f64051b 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -60,6 +60,7 @@ type Globals struct { User *goatcounter.User Site *goatcounter.Site Path string + Base string Flash *zhttp.FlashMessage Static string StaticDomain string @@ -79,11 +80,17 @@ func (g Globals) T(msg string, data ...any) template.HTML { func newGlobals(w http.ResponseWriter, r *http.Request) Globals { ctx := r.Context() + base := goatcounter.Config(ctx).BasePath + path := strings.TrimPrefix(r.URL.Path, base) + if path == "" { + path = "/" + } g := Globals{ Context: ctx, User: goatcounter.GetUser(ctx), Site: goatcounter.GetSite(ctx), - Path: r.URL.Path, + Path: path, + Base: base, Flash: zhttp.ReadFlash(w, r), Static: goatcounter.Config(ctx).URLStatic, Domain: goatcounter.Config(ctx).Domain, @@ -124,7 +131,7 @@ func newGlobals(w http.ResponseWriter, r *http.Request) Globals { return g } -func NewStatic(r chi.Router, dev, goatcounterCom bool) chi.Router { +func NewStatic(r chi.Router, dev, goatcounterCom bool, basePath string) chi.Router { var cache map[string]int if !dev { cache = map[string]int{ @@ -141,6 +148,9 @@ func NewStatic(r chi.Router, dev, goatcounterCom bool) chi.Router { s.Header("/count.js", map[string]string{ "Cross-Origin-Resource-Policy": "cross-origin", }) - r.Get("/*", s.ServeHTTP) + r.Get("/*", func(w http.ResponseWriter, r *http.Request) { + r.URL.Path = strings.TrimPrefix(r.URL.Path, basePath) + s.ServeHTTP(w, r) + }) return r } diff --git a/handlers/i18n.go b/handlers/i18n.go index 5cd48d1e..e14c6802 100644 --- a/handlers/i18n.go +++ b/handlers/i18n.go @@ -140,7 +140,7 @@ func (h i18n) show(w http.ResponseWriter, r *http.Request) error { return zhttp.Template(w, "i18n_show.gohtml", struct { Globals - Base msgfile.File + BaseFile msgfile.File File msgfile.File TOMLFile string FormatLink func(string) string diff --git a/handlers/mw.go b/handlers/mw.go index e8d4e822..2fa9c326 100644 --- a/handlers/mw.go +++ b/handlers/mw.go @@ -37,7 +37,7 @@ var Started time.Time var ( redirect = func(w http.ResponseWriter, r *http.Request) error { zhttp.Flash(w, "Need to log in") - return guru.Errorf(303, "/user/new") + return guru.Errorf(303, goatcounter.Config(r.Context()).BasePath+"/user/new") } loggedIn = auth.Filter(func(w http.ResponseWriter, r *http.Request) error { @@ -76,7 +76,7 @@ var ( Secure: zhttp.CookieSecure, SameSite: zhttp.CookieSameSite, }) - return guru.Errorf(303, "/") + return guru.Errorf(303, goatcounter.Config(r.Context()).BasePath+"/") } if c, err := r.Cookie("access-token"); err == nil && s.Settings.CanView(c.Value) { return nil @@ -220,7 +220,7 @@ func addctx(db zdb.DB, loadSite bool, dashTimeout int) func(http.Handler) http.H func noSites(db zdb.DB, w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { - w.Header().Set("Location", "/") + w.Header().Set("Location", goatcounter.Config(r.Context()).BasePath+"/") w.WriteHeader(307) return } @@ -302,11 +302,12 @@ func noSites(db zdb.DB, w http.ResponseWriter, r *http.Request) { } err := zhttp.Template(w, "serve_newsite.gohtml", struct { + Globals Validate *zvalidate.Validator Error error Email string Cname string - }{&v, tplErr, args.Email, args.Cname}) + }{newGlobals(w, r), &v, tplErr, args.Email, args.Cname}) if err != nil { zlog.Error(err) } diff --git a/handlers/user.go b/handlers/user.go index cd94a12d..321deaaa 100644 --- a/handlers/user.go +++ b/handlers/user.go @@ -536,7 +536,7 @@ func (h user) verify(w http.ResponseWriter, r *http.Request) error { return zhttp.SeeOther(w, "/") } -// Make sure to use the currect cookie, since both "custom.example.com" and +// Make sure to use the correct cookie, since both "custom.example.com" and // "example.goatcounter.com" will work if you're using a custom domain. func cookieDomain(site *goatcounter.Site, r *http.Request) string { if r.Host == site.Domain(r.Context()) { diff --git a/handlers/website.go b/handlers/website.go index 9e9e0ce3..03923062 100644 --- a/handlers/website.go +++ b/handlers/website.go @@ -394,7 +394,7 @@ func (h website) doSignup(w http.ResponseWriter, r *http.Request) error { } }) - return zhttp.SeeOther(w, fmt.Sprintf("%s/user/new", site.URL(r.Context()))) + return zhttp.SeeOther(w, site.URL(r.Context())+"/user/new") } func (h website) forgot(err error, email, turingTest string) zhttp.HandlerFunc { @@ -481,11 +481,7 @@ func (h website) help(w http.ResponseWriter, r *http.Request) error { dc := goatcounter.Config(r.Context()).DomainCount if dc == "" { - dc = Site(r.Context()).Domain(r.Context()) - port := goatcounter.Config(r.Context()).Port - if port != "" { - dc += port - } + dc = Site(r.Context()).SchemelessURL(r.Context()) } cp := chi.URLParam(r, "*") diff --git a/public/backend.js b/public/backend.js index 696fc248..3df65611 100644 --- a/public/backend.js +++ b/public/backend.js @@ -8,6 +8,7 @@ $(document).ready(function() { window.I18N = JSON.parse($('#js-i18n').text()) window.USER_SETTINGS = JSON.parse($('#js-settings').text()) + window.BASE_PATH = $('#js-settings').attr('data-base-path') || "" window.CSRF = $('#js-settings').attr('data-csrf') window.TZ_OFFSET = parseInt($('#js-settings').attr('data-offset'), 10) || 0 window.SITE_FIRST_HIT_AT = $('#js-settings').attr('data-first-hit-at') * 1000 @@ -25,9 +26,9 @@ var report_errors = function() { window.onerror = on_error $(document).on('ajaxError', function(e, xhr, settings, err) { - if (settings.url === '/jserr') // Just in case, otherwise we'll be stuck. + if (settings.url === BASE_PATH + '/jserr') // Just in case, otherwise we'll be stuck. return - if (settings.url === '/load-widget') + if (settings.url === BASE_PATH + '/load-widget') return var msg = T("error/load-url", {url: settings.url, error: err}) console.error(msg) @@ -53,7 +54,7 @@ return jQuery.ajax({ - url: '/jserr', + url: BASE_PATH + '/jserr', method: 'POST', data: {msg: msg, url: url, line: line, column: column, stack: (err||{}).stack, ua: navigator.userAgent, loc: window.location+''}, }) @@ -125,7 +126,7 @@ e.preventDefault() jQuery.ajax({ - url: '/settings/main/ip', + url: BASE_PATH + '/settings/main/ip', success: function(data) { var input = $('[name="settings.ignore_ips"]'), current = input.val().split(','). @@ -161,7 +162,7 @@ // Update redirect link. $('#settings-secret').on('change', function(e) { - $('#secret-url').val(`${location.protocol}//${location.host}?access-token=${this.value}`) + $('#secret-url').val(`${location.protocol}//${location.host}${BASE_PATH}?access-token=${this.value}`) }).trigger('change') } @@ -193,7 +194,7 @@ return jQuery.ajax({ - url: '/user/dashboard/widget/' + this.selectedOptions[0].value, + url: BASE_PATH + '/user/dashboard/widget/' + this.selectedOptions[0].value, success: function(data) { var i = 1 + $('.index').toArray().map((e) => parseInt(e.value, 10)).sort().pop(), html = $(data.replace(/widgets([\[_])0([\]_])/g, `widgets$1${i}$2`)) diff --git a/public/dashboard.js b/public/dashboard.js index 37cbdf14..b13e8f6c 100644 --- a/public/dashboard.js +++ b/public/dashboard.js @@ -26,7 +26,7 @@ return let cid = $('#js-connect-id').text() - window.WEBSOCKET = new WebSocket((location.protocol === 'https:' ? 'wss://' : 'ws://') + document.location.host + '/loader?id=' + cid) + window.WEBSOCKET = new WebSocket((location.protocol === 'https:' ? 'wss://' : 'ws://') + document.location.host + BASE_PATH + '/loader?id=' + cid) window.WEBSOCKET.onmessage = function(e) { let msg = JSON.parse(e.data), wid = $(`#dash-widgets div[data-widget=${msg.id}]`) @@ -47,7 +47,7 @@ btn = $(this), pos = btn.offset(), wid = btn.closest('[data-widget]').attr('data-widget'), - url = '/user/dashboard/' + wid, + url = BASE_PATH + '/user/dashboard/' + wid, remove = function() { pop.remove() $(document.body).off('.unpop') @@ -94,7 +94,7 @@ data['total'] = $('.js-total-utc').text() jQuery.ajax({ - url: '/load-widget', + url: BASE_PATH + '/load-widget', type: 'get', data: append_period(data), success: function(data) { @@ -112,7 +112,7 @@ // Reload all widgets on the dashboard. var reload_dashboard = function(done) { jQuery.ajax({ - url: '/', + url: BASE_PATH + '/', data: append_period({ daily: $('#daily').is(':checked'), max: get_original_scale(), @@ -326,7 +326,7 @@ var done = paginate_button($(this), () => { jQuery.ajax({ - url: '/user/view', + url: BASE_PATH + '/user/view', method: 'POST', data: { csrf: CSRF, @@ -387,7 +387,7 @@ $('.list-ref-pages').remove() var done = paginate_button(btn, () => { jQuery.ajax({ - url: '/pages-by-ref', + url: BASE_PATH + '/pages-by-ref', data: append_period({name: btn.text()}), success: function(data) { p.append(data.html) @@ -677,7 +677,7 @@ pages = $(this).closest('.pages-list') let done = paginate_button(btn, () => { jQuery.ajax({ - url: '/load-widget', + url: BASE_PATH + '/load-widget', data: append_period({ widget: pages.attr('data-widget'), daily: $('#daily').is(':checked'), @@ -752,7 +752,7 @@ push_query({showrefs: path}) let done = paginate_button(btn , () => { jQuery.ajax({ - url: '/load-widget', + url: BASE_PATH + '/load-widget', data: append_period({ widget: widget, key: path, @@ -806,7 +806,7 @@ rows.data('pagesize', rows.children().length) let done = paginate_button($(this), () => { jQuery.ajax({ - url: '/load-widget', + url: BASE_PATH + '/load-widget', data: append_period({ widget: chart.attr('data-widget'), total: get_total(), @@ -840,7 +840,7 @@ l.addClass('loading') var done = paginate_button(l, () => { jQuery.ajax({ - url: '/load-widget', + url: BASE_PATH + '/load-widget', data: append_period({ widget: widget, key: key, diff --git a/public/help.js b/public/help.js index a1f8fcf9..0a0402a3 100644 --- a/public/help.js +++ b/public/help.js @@ -1,5 +1,7 @@ +let helpIndex = window.location.pathname.lastIndexOf('/help'); +let basePath = window.location.pathname.slice(0, helpIndex === -1 ? 0 : helpIndex) document.querySelector('select').addEventListener('change', function(e) { - window.location = '/code/' + this.value + window.location = basePath + '/help/' + this.value }) document.querySelector('.show-contact').addEventListener('click', function(e) { e.preventDefault() @@ -26,7 +28,7 @@ if (expand) }) }) -if (window.location.pathname === '/help/visitor-counter') { +if (window.location.pathname === basePath + '/help/visitor-counter') { var t = setInterval(function() { if (window.goatcounter && window.goatcounter.visit_count) { clearInterval(t) diff --git a/public/shared.css b/public/shared.css index 9ca52131..6b990dfb 100644 --- a/public/shared.css +++ b/public/shared.css @@ -1,23 +1,4 @@ /*** Fonts ***/ -@font-face { - font-family: 'Lato'; font-style: normal; font-weight: 400; font-display: fallback; - src: local('Lato'), local('Lato Regular'), - url('/fonts/latolatin.woff2') format('woff2'), - url('./fonts/latolatin.woff2') format('woff2'); -} -@font-face { - font-family: 'Lato'; font-style: normal; font-weight: 700; font-display: fallback; - src: local('Lato Bold'), local('Lato Bold'), - url('/fonts/latolatin-bold.woff2') format('woff2'), - url('./fonts/latolatin-bold.woff2') format('woff2'); -} -@font-face { - font-family: 'Lato'; font-style: italic; font-weight: 400; font-display: fallback; - src: local('Lato Italic'), local('Lato Italic'), - url('/fonts/latolatin-italic.woff2') format('woff2'), - url('./fonts/latolatin-italic.woff2') format('woff2'); -} - html { font: 16px/150% 'Lato', sans-serif; text-size-adjust: none; -webkit-text-size-adjust: none; background-color: var(--backdrop); color: var(--text); tab-size: 4; } html, body { margin: 0; } diff --git a/settings.go b/settings.go index c3249ef6..9842da8e 100644 --- a/settings.go +++ b/settings.go @@ -405,7 +405,7 @@ func (ss SiteSettings) CollectFlags(ctx context.Context) []CollectFlag { return []CollectFlag{ { Label: z18n.T(ctx, "data-collect/label/sessions|Sessions"), - Help: z18n.T(ctx, "data-collect/help/sessions|%[Track unique visitors] for up to 8 hours; if you disable this then someone pressing e.g. F5 to reload the page will just show as 2 pageviews instead of 1.", z18n.Tag("a", `href="/help/sessions"`)), + Help: z18n.T(ctx, "data-collect/help/sessions|%[Track unique visitors] for up to 8 hours; if you disable this then someone pressing e.g. F5 to reload the page will just show as 2 pageviews instead of 1.", z18n.Tag("a", fmt.Sprintf(`href="%s/help/sessions"`, Config(ctx).BasePath))), Flag: CollectSession, }, { diff --git a/site.go b/site.go index 34d7cb9c..583d88f0 100644 --- a/site.go +++ b/site.go @@ -523,17 +523,22 @@ func (s Site) Display(ctx context.Context) string { return fmt.Sprintf("%s.%s", s.Code, znet.RemovePort(Config(ctx).Domain)) } -// URL to this site. -func (s Site) URL(ctx context.Context) string { +// URL to this site, without the scheme. +func (s Site) SchemelessURL(ctx context.Context) string { if s.Cname != nil && s.CnameSetupAt != nil { - return fmt.Sprintf("http%s://%s%s", - map[bool]string{true: "", false: "s"}[Config(ctx).Dev], - *s.Cname, Config(ctx).Port) + return *s.Cname + Config(ctx).Port + Config(ctx).BasePath } - return fmt.Sprintf("http%s://%s.%s%s", - map[bool]string{true: "", false: "s"}[Config(ctx).Dev], - s.Code, Config(ctx).Domain, Config(ctx).Port) + return fmt.Sprintf("%s.%s%s%s", + s.Code, Config(ctx).Domain, Config(ctx).Port, Config(ctx).BasePath) +} + +// URL to this site. +func (s Site) URL(ctx context.Context) string { + if Config(ctx).Dev { + return "http://" + s.SchemelessURL(ctx) + } + return "https://" + s.SchemelessURL(ctx) } // LinkDomainURL creates a valid url to the configured LinkDomain. diff --git a/tpl/_backend_bottom.gohtml b/tpl/_backend_bottom.gohtml index 31da781b..1f506a36 100644 --- a/tpl/_backend_bottom.gohtml +++ b/tpl/_backend_bottom.gohtml @@ -8,6 +8,7 @@ data-offset="{{.User.Settings.Timezone.Offset}}" data-first-hit-at="{{.Site.FirstHitAt.Unix}}" data-websocket="{{.Websocket}}" + {{if .Base}}data-base-path="{{.Base}}"{{end}} {{if .User.ID}}data-csrf="{{.User.CSRFToken}}"{{end}} > {{- .User.Settings.String | unsafe_js -}} diff --git a/tpl/_backend_signin.gohtml b/tpl/_backend_signin.gohtml index 712e7608..6b212a98 100644 --- a/tpl/_backend_signin.gohtml +++ b/tpl/_backend_signin.gohtml @@ -1,4 +1,4 @@ -
+
@@ -7,4 +7,4 @@
-

{{.T "button/forgot-password|Forgot password?"}}

+

{{.T "button/forgot-password|Forgot password?"}}

diff --git a/tpl/_backend_top.gohtml b/tpl/_backend_top.gohtml index ec4e7db7..c1ae085c 100644 --- a/tpl/_backend_top.gohtml +++ b/tpl/_backend_top.gohtml @@ -12,6 +12,23 @@ {{end}} + @@ -45,29 +62,29 @@ {{range $i, $s := .SubSites -}} {{- if gt $i 0 -}}|{{- end -}} {{if $.GoatcounterCom}} {{$s}} - {{else}} {{$s}} + {{else}} {{$s}} {{end -}} {{end}} {{- end -}} {{else if has_prefix .Path "/settings/sites/remove/"}} - ←︎ {{.T "top-nav/back|Back"}} + ←︎ {{.T "top-nav/back|Back"}} {{else if has_prefix .Path "/settings/purge/confirm"}} - ←︎ {{.T "top-nav/back|Back"}} + ←︎ {{.T "top-nav/back|Back"}} {{else if has_prefix .Path "/i18n/"}} - ←︎ {{.T "top-nav/back|Back"}} + ←︎ {{.T "top-nav/back|Back"}} {{else if has_prefix .Path "/bosmang/"}} - ←︎ {{.T "top-nav/back|Back"}} + ←︎ {{.T "top-nav/back|Back"}} {{else}} - ←︎ {{.T "top-nav/dashboard|Dashboard"}} + ←︎ {{.T "top-nav/dashboard|Dashboard"}} {{end}}
- {{.T "top-nav/documentation|Help"}} | - {{if .User.AccessSettings}}{{.T "top-nav/settings|Settings"}} |{{end}} - {{.User.EmailShort}} | -
+ {{.T "top-nav/documentation|Help"}} | + {{if .User.AccessSettings}}{{.T "top-nav/settings|Settings"}} |{{end}} + {{.User.EmailShort}} | +
@@ -84,7 +101,7 @@ "timezone-offset" .User.Settings.Timezone.OffsetDisplay )}}
- + {{- end -}} @@ -94,7 +111,7 @@ {{if and .User.ID (before .Site.CreatedAt "2024-04-07")}}
The default theme colours are now set from your system; you can change it back to the - previous by changing it in your user settings + previous by changing it in your user settingsdon’t show again
diff --git a/tpl/_bottom_links.gohtml b/tpl/_bottom_links.gohtml index 9454921f..748a85f1 100644 --- a/tpl/_bottom_links.gohtml +++ b/tpl/_bottom_links.gohtml @@ -1,9 +1,9 @@