added support for running gowatch under a reverse proxy path

This commit is contained in:
BroodjeAap 2023-02-05 09:32:52 +00:00
parent ecac18a0b7
commit 1a83bdcd86
16 changed files with 177 additions and 96 deletions

View file

@ -13,6 +13,7 @@ A change detection server that can notify through various services, written in G
- [Pruning](#pruning)
- [Proxy](#proxy)
- [Proxy Pools](#proxy-pools)
- [Reverse Proxy](#reverse-proxy)
- [Browserless](#browserless)
- [Authentication](#Authentication)
- [Filters](#filters)
@ -222,6 +223,15 @@ cache_peer proxy2.com parent 3128 0 round-robin no-query login=user:pass
An example `squid.conf` can be found in [docs/proxy/squid-1.conf](docs/proxy/squid-1.conf).
## Reverse Proxy
GoWatch can be run behind a reverse proxy, if it's hosted under a subdomain (https://gowatch.domain.tld), not changes to the config are needed.
But if you want to run GoWatch under a path (https://domain.tld/gowatch), you can set the `gin.urlprefix` value in the config or the `GOWATCH_URLPREFIX` environment variable can be used.
```
gin:
urlprefix: "/gowatch"
```
## Browserless
Some websites (Amazon for example) don't send all content on the first request, it's added later through javascript.

View file

@ -47,5 +47,6 @@ browserless:
url: http://your.browserless:1234
gin:
debug: false
urlprefix: "/"
schedule:
delay: "5s"

165
main.go
View file

@ -16,6 +16,7 @@ import (
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"strconv"
"strings"
@ -46,6 +47,7 @@ type Web struct {
db *gorm.DB // gorm db instance
notifiers map[string]notifiers.Notifier // holds notifierName -> notifier
startupWarnings []string // simple list of warnings/errors found during startup, displayed on / page
urlPrefix string // allows gowatch to run behind a reverse proxy on a subpath
}
// NewWeb creates a new web instance and calls .init() before returning it
@ -168,39 +170,49 @@ func (web *Web) initDB() {
func (web *Web) initRouter() {
web.router = gin.Default()
web.initTemplates()
web.router.HTMLRender = web.templates
if viper.IsSet("gin.urlprefix") {
urlPrefix := viper.GetString("gin.urlprefix")
urlPrefix = path.Join("/", urlPrefix) + "/"
web.urlPrefix = urlPrefix
log.Println("Running under path: " + web.urlPrefix)
} else {
web.urlPrefix = "/"
}
staticFS, err := fs.Sub(EMBED_FS, "static")
if err != nil {
log.Fatalln("Could not load static embed fs")
}
web.router.StaticFS("/static", http.FS(staticFS))
web.router.StaticFileFS("/favicon.ico", "favicon.ico", http.FS(staticFS))
web.router.StaticFS(web.urlPrefix+"static", http.FS(staticFS))
web.router.StaticFileFS(web.urlPrefix+"favicon.ico", "favicon.ico", http.FS(staticFS))
web.initTemplates()
web.router.HTMLRender = web.templates
gowatch := web.router.Group(web.urlPrefix)
web.router.GET("/", web.index)
gowatch.GET("", web.index)
gowatch.GET("watch/view/:id", web.watchView)
gowatch.GET("watch/edit/:id", web.watchEdit)
gowatch.GET("watch/create", web.watchCreate)
gowatch.POST("watch/create", web.watchCreatePost)
gowatch.POST("watch/update", web.watchUpdate)
gowatch.POST("watch/delete", web.deleteWatch)
gowatch.GET("watch/export/:id", web.exportWatch)
gowatch.POST("watch/import/:id", web.importWatch)
web.router.GET("/watch/view/:id", web.watchView)
web.router.GET("/watch/edit/:id", web.watchEdit)
web.router.GET("/watch/create", web.watchCreate)
web.router.POST("/watch/create", web.watchCreatePost)
web.router.POST("/watch/update", web.watchUpdate)
web.router.POST("/watch/delete", web.deleteWatch)
web.router.GET("/watch/export/:id", web.exportWatch)
web.router.POST("/watch/import/:id", web.importWatch)
gowatch.GET("cache/view", web.cacheView)
gowatch.POST("cache/clear", web.cacheClear)
web.router.GET("/cache/view", web.cacheView)
web.router.POST("/cache/clear", web.cacheClear)
gowatch.GET("notifiers/view", web.notifiersView)
gowatch.POST("notifiers/test", web.notifiersTest)
web.router.GET("/notifiers/view", web.notifiersView)
web.router.POST("/notifiers/test", web.notifiersTest)
web.router.GET("/backup/view", web.backupView)
web.router.GET("/backup/create", web.backupCreate)
web.router.POST("/backup/test", web.backupTest)
web.router.POST("/backup/restore", web.backupRestore)
web.router.POST("/backup/delete", web.backupDelete)
web.router.GET("/backup/download/:id", web.backupDownload)
gowatch.GET("backup/view", web.backupView)
gowatch.GET("backup/create", web.backupCreate)
gowatch.POST("backup/test", web.backupTest)
gowatch.POST("backup/restore", web.backupRestore)
gowatch.POST("backup/delete", web.backupDelete)
gowatch.GET("backup/download/:id", web.backupDownload)
web.router.SetTrustedProxies(nil)
}
@ -448,14 +460,18 @@ func (web *Web) index(c *gin.Context) {
}
c.HTML(http.StatusOK, "index", gin.H{
"watches": watches,
"warnings": web.startupWarnings,
"watches": watches,
"warnings": web.startupWarnings,
"urlPrefix": web.urlPrefix,
})
}
// notifiersView (/notifiers/view) shows the notifiers and a test button
func (web *Web) notifiersView(c *gin.Context) {
c.HTML(http.StatusOK, "notifiersView", web.notifiers)
c.HTML(http.StatusOK, "notifiersView", gin.H{
"notifiers": web.notifiers,
"urlPrefix": web.urlPrefix,
})
}
// notifiersTest (/notifiers/test) sends a test message to notifier_name
@ -463,11 +479,11 @@ func (web *Web) notifiersTest(c *gin.Context) {
notifierName := c.PostForm("notifier_name")
notifier, exists := web.notifiers[notifierName]
if !exists {
c.Redirect(http.StatusSeeOther, "/notifiers/view")
c.Redirect(http.StatusSeeOther, web.urlPrefix+"notifiers/view")
return
}
notifier.Message("GoWatch Test")
c.Redirect(http.StatusSeeOther, "/notifiers/view")
c.Redirect(http.StatusSeeOther, web.urlPrefix+"notifiers/view")
}
// watchCreate (/watch/create) allows user to create a new watch
@ -486,6 +502,7 @@ func (web *Web) watchCreate(c *gin.Context) {
}
c.HTML(http.StatusOK, "watchCreate", gin.H{
"templates": templates,
"urlPrefix": web.urlPrefix,
})
}
@ -506,7 +523,7 @@ func (web *Web) watchCreatePost(c *gin.Context) {
if templateID == 0 { // empty new watch
web.db.Create(&watch)
c.Redirect(http.StatusSeeOther, fmt.Sprintf("/watch/edit/%d", watch.ID))
c.Redirect(http.StatusSeeOther, fmt.Sprintf(web.urlPrefix+"watch/edit/%d", watch.ID))
return // nothing else to do
}
@ -552,7 +569,7 @@ func (web *Web) watchCreatePost(c *gin.Context) {
}
if templateID > len(templateFiles) {
log.Println("/watch/create POSTed with", templateID, "but only", len(templateFiles), "templates")
log.Println(web.urlPrefix+"watch/create POSTed with", templateID, "but only", len(templateFiles), "templates")
c.AbortWithError(http.StatusBadRequest, err)
return
}
@ -621,7 +638,7 @@ func (web *Web) watchCreatePost(c *gin.Context) {
}
}
c.Redirect(http.StatusSeeOther, fmt.Sprintf("/watch/edit/%d", watch.ID))
c.Redirect(http.StatusSeeOther, fmt.Sprintf(web.urlPrefix+"watch/edit/%d", watch.ID))
}
// deleteWatch (/watch/delete) removes a watch and it's cronjobs
@ -629,7 +646,7 @@ func (web *Web) deleteWatch(c *gin.Context) {
id, err := strconv.Atoi(c.PostForm("watch_id"))
if err != nil {
log.Println(err)
c.Redirect(http.StatusSeeOther, "/")
c.Redirect(http.StatusSeeOther, web.urlPrefix)
return
}
@ -648,7 +665,7 @@ func (web *Web) deleteWatch(c *gin.Context) {
web.db.Delete(&Filter{}, "watch_id = ?", id)
web.db.Delete(&Watch{}, id)
c.Redirect(http.StatusSeeOther, "/")
c.Redirect(http.StatusSeeOther, web.urlPrefix)
}
// watchView (/watch/view) shows the watch page with a graph and/or a table of stored values
@ -703,6 +720,7 @@ func (web *Web) watchView(c *gin.Context) {
"numericalMap": numericalMap,
"categoricalMap": categoricalMap,
"colorMap": colorMap,
"urlPrefix": web.urlPrefix,
})
}
@ -734,6 +752,7 @@ func (web *Web) watchEdit(c *gin.Context) {
"Filters": filters,
"Connections": connections,
"Notifiers": notifiers,
"urlPrefix": web.urlPrefix,
})
}
@ -803,12 +822,15 @@ func (web *Web) watchUpdate(c *gin.Context) {
web.db.Create(&newConnections)
}
c.Redirect(http.StatusSeeOther, fmt.Sprintf("/watch/edit/%d", watch.ID))
c.Redirect(http.StatusSeeOther, fmt.Sprintf(web.urlPrefix+"watch/edit/%d", watch.ID))
}
// cacheView (/cache/view) shows the items in the web.urlCache
func (web *Web) cacheView(c *gin.Context) {
c.HTML(http.StatusOK, "cacheView", web.urlCache)
c.HTML(http.StatusOK, "cacheView", gin.H{
"cache": web.urlCache,
"urlPrefix": web.urlPrefix,
})
}
// cacheClear (/cache/clear) clears all items in web.urlCache
@ -820,15 +842,24 @@ func (web *Web) cacheClear(c *gin.Context) {
// backupView (/backup/view) lists the stored backups
func (web *Web) backupView(c *gin.Context) {
if !viper.IsSet("database.backup") {
c.HTML(http.StatusOK, "backupView", gin.H{"Error": "database.backup not set"})
c.HTML(http.StatusOK, "backupView", gin.H{
"Error": "database.backup not set",
"urlPrefix": web.urlPrefix,
})
return
}
if !viper.IsSet("database.backup.schedule") {
c.HTML(http.StatusOK, "backupView", gin.H{"Error": "database.backup.schedule not set"})
c.HTML(http.StatusOK, "backupView", gin.H{
"Error": "database.backup.schedule not set",
"urlPrefix": web.urlPrefix,
})
return
}
if !viper.IsSet("database.backup.path") {
c.HTML(http.StatusOK, "backupView", gin.H{"Error": "database.backup.path not set"})
c.HTML(http.StatusOK, "backupView", gin.H{
"Error": "database.backup.path not set",
"urlPrefix": web.urlPrefix,
})
return
}
@ -836,13 +867,19 @@ func (web *Web) backupView(c *gin.Context) {
backupDir, err := filepath.Abs(filepath.Dir(backupPath))
if err != nil {
c.HTML(http.StatusOK, "backupView", gin.H{"Error": err})
c.HTML(http.StatusOK, "backupView", gin.H{
"Error": err,
"urlPrefix": web.urlPrefix,
})
return
}
filesInBackupDir, err := ioutil.ReadDir(backupDir)
if err != nil {
c.HTML(http.StatusOK, "backupView", gin.H{"Error": err})
c.HTML(http.StatusOK, "backupView", gin.H{
"Error": err,
"urlPrefix": web.urlPrefix,
})
return
}
@ -852,7 +889,10 @@ func (web *Web) backupView(c *gin.Context) {
filePaths = append(filePaths, fullPath)
}
c.HTML(http.StatusOK, "backupView", gin.H{"Backups": filePaths})
c.HTML(http.StatusOK, "backupView", gin.H{
"Backups": filePaths,
"urlPrefix": web.urlPrefix,
})
}
// backupCreate (/backup/create) creates a new backup
@ -875,7 +915,7 @@ func (web *Web) backupCreate(c *gin.Context) {
c.HTML(http.StatusBadRequest, "backupView", gin.H{"Error": err})
return
}
c.Redirect(http.StatusSeeOther, "/backup/view")
c.Redirect(http.StatusSeeOther, web.urlPrefix+"backup/view")
}
func (web *Web) scheduledBackup() {
@ -984,7 +1024,7 @@ func (web *Web) backupTest(c *gin.Context) {
return
}
if importID < -1 {
c.Redirect(http.StatusSeeOther, "/backup/view")
c.Redirect(http.StatusSeeOther, web.urlPrefix+"backup/view")
return
}
if !viper.IsSet("database.backup") {
@ -1016,6 +1056,7 @@ func (web *Web) backupTest(c *gin.Context) {
c.HTML(http.StatusOK, "backupTest", gin.H{
"Backup": backup,
"BackupPath": backupFullPath,
"urlPrefix": web.urlPrefix,
})
}
@ -1091,20 +1132,29 @@ func (web *Web) backupRestore(c *gin.Context) {
return
}
if importID < -1 {
c.Redirect(http.StatusSeeOther, "/backup/view")
c.Redirect(http.StatusSeeOther, web.urlPrefix+"backup/view")
return
}
if !viper.IsSet("database.backup") {
c.HTML(http.StatusOK, "backupRestore", gin.H{"Error": "database.backup not set"})
c.HTML(http.StatusOK, "backupRestore", gin.H{
"Error": "database.backup not set",
"urlPrefix": web.urlPrefix,
})
return
}
if !viper.IsSet("database.backup.schedule") {
c.HTML(http.StatusOK, "backupRestore", gin.H{"Error": "database.backup.schedule not set"})
c.HTML(http.StatusOK, "backupRestore", gin.H{
"Error": "database.backup.schedule not set",
"urlPrefix": web.urlPrefix,
})
return
}
if !viper.IsSet("database.backup.path") {
c.HTML(http.StatusOK, "backupRestore", gin.H{"Error": "database.backup.path not set"})
c.HTML(http.StatusOK, "backupRestore", gin.H{
"Error": "database.backup.path not set",
"urlPrefix": web.urlPrefix,
})
return
}
@ -1117,7 +1167,10 @@ func (web *Web) backupRestore(c *gin.Context) {
}
if err != nil {
c.HTML(http.StatusOK, "backupRestore", gin.H{"Error": err})
c.HTML(http.StatusOK, "backupRestore", gin.H{
"Error": err,
"urlPrefix": web.urlPrefix,
})
return
}
@ -1149,13 +1202,17 @@ func (web *Web) backupRestore(c *gin.Context) {
return nil
})
if err != nil {
c.HTML(http.StatusOK, "backupRestore", gin.H{"Error": err})
c.HTML(http.StatusOK, "backupRestore", gin.H{
"Error": err,
"urlPrefix": web.urlPrefix,
})
return
}
c.HTML(http.StatusOK, "backupRestore", gin.H{
"Backup": backup,
"BackupPath": backupFullPath,
"urlPrefix": web.urlPrefix,
})
}
@ -1167,7 +1224,7 @@ func (web *Web) backupDelete(c *gin.Context) {
return
}
if importID <= 0 {
c.Redirect(http.StatusSeeOther, "/backup/view")
c.Redirect(http.StatusSeeOther, web.urlPrefix+"backup/view")
return
}
@ -1210,7 +1267,7 @@ func (web *Web) backupDelete(c *gin.Context) {
c.HTML(http.StatusOK, "backupView", gin.H{"Error": err})
return
}
c.Redirect(http.StatusSeeOther, "/backup/view")
c.Redirect(http.StatusSeeOther, web.urlPrefix+"backup/view")
}
// backupDownload (/backup/download) serves the backup file in index 'id'
@ -1221,7 +1278,7 @@ func (web *Web) backupDownload(c *gin.Context) {
return
}
if importID < 0 {
c.Redirect(http.StatusSeeOther, "/backup/view")
c.Redirect(http.StatusSeeOther, web.urlPrefix+"backup/view")
return
}
@ -1397,7 +1454,7 @@ func (web *Web) importWatch(c *gin.Context) {
}
}
c.Redirect(http.StatusSeeOther, fmt.Sprintf("/watch/edit/%d", watchID))
c.Redirect(http.StatusSeeOther, fmt.Sprintf(web.urlPrefix+"watch/edit/%d", watchID))
}
func main() {

View file

@ -1,11 +1,13 @@
// @ts-ignore
var urlPrefix = getURLPrefix();
function testSubmit() {
var form = document.getElementById("uploadForm");
form.action = "/backup/test";
form.action = urlPrefix + "backup/test";
form.submit();
}
function restoreSubmit() {
var form = document.getElementById("uploadForm");
form.action = "/backup/restore";
form.action = urlPrefix + "backup/restore";
form.submit();
}
function initUploadSubmit() {

View file

@ -1,12 +1,14 @@
// @ts-ignore
let urlPrefix = getURLPrefix();
function testSubmit() {
let form = document.getElementById("uploadForm") as HTMLFormElement;
form.action = "/backup/test";
form.action = urlPrefix + "backup/test";
form.submit();
}
function restoreSubmit() {
let form = document.getElementById("uploadForm") as HTMLFormElement;
form.action = "/backup/restore";
form.action = urlPrefix + "backup/restore";
form.submit();
}

View file

@ -32,6 +32,8 @@ var __spread = (this && this.__spread) || function () {
function onTypeChange(node) {
var e_1, _a, e_2, _b;
if (node === void 0) { node = null; }
// @ts-ignore
var urlPrefix = getURLPrefix();
var select = document.getElementById("typeInput");
var type = select.value;
var var1Div = document.getElementById("var1Div");
@ -1189,7 +1191,7 @@ function clearCache() {
return; // do nothing
}
var data = new URLSearchParams();
fetch("/cache/clear", {
fetch(urlPrefix + "cache/clear", {
method: "POST"
}).then(function (response) {
if (response.ok) {

View file

@ -1,4 +1,7 @@
function onTypeChange(node: DiagramNode | null = null){
// @ts-ignore
let urlPrefix = getURLPrefix();
let select = document.getElementById("typeInput") as HTMLSelectElement;
let type = select.value;
@ -1325,7 +1328,7 @@ function clearCache(){
return // do nothing
}
let data = new URLSearchParams();
fetch("/cache/clear", {
fetch(urlPrefix+"cache/clear", {
method: "POST"
}).then((response) => {
if(response.ok){

View file

@ -3,7 +3,7 @@ GoWatch Backups
{{end}}
{{define "head"}}
<script src="/static/backup.js"></script>
<script src="{{.urlPrefix}}static/backup.js"></script>
{{ end }}
{{define "content"}}
@ -30,7 +30,7 @@ GoWatch Backups
<tbody>
<tr>
<td>
<form id="uploadForm" action="/backup/restore" enctype="multipart/form-data" method="POST">
<form id="uploadForm" action="{{.urlPrefix}}backup/restore" enctype="multipart/form-data" method="POST">
<input type="hidden" value="-1" name="id">
<input class="form-control" type="file" id="upload" name="upload">
</form>
@ -48,25 +48,25 @@ GoWatch Backups
<tr>
<td class="h5">{{ $backup }}</td>
<td>
<form action="/backup/test" enctype="multipart/form-data" method="POST">
<form action="{{$.urlPrefix}}backup/test" enctype="multipart/form-data" method="POST">
<input type="hidden" value="{{ $i }}" name="id">
<button class="btn btn-success">Test</button>
</form>
</td>
<td>
<form action="/backup/restore" enctype="multipart/form-data" method="POST">
<form action="{{$.urlPrefix}}backup/restore" enctype="multipart/form-data" method="POST">
<input type="hidden" value="{{ $i }}" name="id">
<button class="btn btn-warning">Restore</button>
</form>
</td>
<td>
<form action="/backup/delete" enctype="multipart/form-data" method="POST">
<form action="{{$.urlPrefix}}backup/delete" enctype="multipart/form-data" method="POST">
<input type="hidden" value="{{ $i }}" name="id">
<button class="btn btn-danger">Delete</button>
</form>
</td>
<td>
<a href="/backup/download/{{ $i }}" class="btn btn-secondary">
<a href="{{$.urlPrefix}}backup/download/{{ $i }}" class="btn btn-secondary">
Download
</a>
</td>
@ -74,7 +74,7 @@ GoWatch Backups
{{ end }}
</tbody>
</table>
<a href="/backup/create" class="btn btn-warning btn-lg">
<a href="{{.urlPrefix}}backup/create" class="btn btn-warning btn-lg">
Backup Now
</a>
</div>

View file

@ -4,9 +4,13 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/static/bootstrap.5.2.0.min.css" rel="stylesheet">
<link href="/static/style.css" rel="stylesheet">
<link href="{{.urlPrefix}}static/bootstrap.5.2.0.min.css" rel="stylesheet">
<link href="{{.urlPrefix}}static/style.css" rel="stylesheet">
<script>
function getURLPrefix() {
return "{{.urlPrefix}}";
}
</script>
<title>{{ template "title" .}}</title>
{{ template "head" .}}
</head>
@ -21,21 +25,21 @@
<div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav me-auto mb-2 mb-md-0">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="/">Home</a>
<a class="nav-link active" aria-current="page" href="{{.urlPrefix}}">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" aria-current="page" href="/notifiers/view">Notifiers</a>
<a class="nav-link" aria-current="page" href="{{.urlPrefix}}notifiers/view">Notifiers</a>
</li>
<li class="nav-item">
<a class="nav-link" aria-current="page" id="newWatchLink" href="/watch/create">New</a>
<a class="nav-link" aria-current="page" id="newWatchLink" href="{{.urlPrefix}}watch/create">New</a>
</li>
<li class="nav-item">
<a class="nav-link" aria-current="page" href="/backup/view">Backups</a>
<a class="nav-link" aria-current="page" href="{{.urlPrefix}}backup/view">Backups</a>
</li>
{{ template "navbar" .}}
</ul>
<div class="d-flex">
<a class="nav-link btn" href="/config">Config</a>
<a class="nav-link btn" href="{{.urlPrefix}}config">Config</a>
</div>
</div>
</div>
@ -64,8 +68,8 @@
<span class="text-muted">Broodjeaap 2023</span>
</div>
</footer>
<script src="/static/bootstrap.5.2.0.bundle.min.js"></script>
<script src="/static/script.js"></script>
<script src="{{.urlPrefix}}static/bootstrap.5.2.0.bundle.min.js"></script>
<script src="{{.urlPrefix}}static/script.js"></script>
{{ template "scripts" .}}
</body>
</html>

View file

@ -3,11 +3,11 @@ GoWatch URL Cache
{{end}}
{{define "content"}}
{{ range $url, $content := . }}
{{ range $url, $content := .cache }}
<div class="card text-center">
<div class="card-header">
{{ $url }}
<form action="/cache/clear" method="post">
<form action="{{$.urlPrefix}}cache/clear" method="post">
<input type="hidden" name="url" value="{{ $url }}">
<input type="submit" class="btn btn-danger btn-sm" value="Clear">
</form>

View file

@ -28,7 +28,7 @@ GoWatch
</thead>
<tbody>
{{ range .watches }}
<tr class="pointer" onclick="window.location='/watch/view/{{ .ID }}'">
<tr class="pointer" onclick="window.location='{{$.urlPrefix}}watch/view/{{ .ID }}'">
<td class="h3">{{ .Name }}</td>
{{ if .CronEntry }}
<td class="h3">{{ .CronEntry.Prev.Format "2006-01-02 15:04:05" }}</td>
@ -41,10 +41,10 @@ GoWatch
{{ .LastValue }}
</td>
<td>
<a href="/watch/edit/{{ .ID }}" class="btn btn-success">Edit</a>
<a href="{{$.urlPrefix}}watch/edit/{{ .ID }}" class="btn btn-success">Edit</a>
</td>
<td>
<form action="/watch/delete" method="post">
<form action="{{$.urlPrefix}}watch/delete" method="post">
<input type="hidden" name="watch_id" value="{{ .ID }}">
<input type="submit" class="btn btn-danger" value="Delete">
</form>

View file

@ -11,11 +11,11 @@ GoWatch
</tr>
</thead>
<tbody>
{{ range $name, $notifier := . }}
{{ range $name, $notifier := .notifiers }}
<tr>
<td class="h3">{{ $name }}</td>
<td>
<form action="/notifiers/test" method="post">
<form action="{{.urlPrefix}}notifiers/test" method="post">
<input type="hidden" name="notifier_name" value="{{ $name }}">
<input type="submit" class="btn btn-primary" value="Test">
</form>

View file

@ -3,12 +3,12 @@ GoWatch Create
{{end}}
{{define "head"}}
<script src="/static/create.js"></script>
<script src="{{.urlPrefix}}static/create.js"></script>
{{ end }}
{{define "content"}}
<form action="/watch/create" enctype="multipart/form-data" method="POST">
<form action="{{.urlPrefix}}watch/create" enctype="multipart/form-data" method="POST">
<div class="container">
<div class="row g-3 justify-content-center">
<div class="col-auto">

View file

@ -1,6 +1,6 @@
{{define "head"}}
<script src="/static/diagram.js"></script>
<script src="/static/edit.js"></script>
<script src="{{.urlPrefix}}static/diagram.js"></script>
<script src="{{.urlPrefix}}static/edit.js"></script>
{{ end }}
{{define "content"}}
<div class="canvas_parent">
@ -34,7 +34,7 @@ GoWatch Edit {{ .Watch.Name }}
</button>
</td>
<td>
<a href="/watch/export/{{ .Watch.ID }}" class="btn btn-success btn-sm">
<a href="{{.urlPrefix}}watch/export/{{ .Watch.ID }}" class="btn btn-success btn-sm">
Export Watch
</a>
</td>
@ -49,7 +49,7 @@ GoWatch Edit {{ .Watch.Name }}
</button>
</td>
<td>
<a href="/watch/view/{{ .Watch.ID }}" class="btn btn-outline-secondary btn-sm">
<a href="{{.urlPrefix}}watch/view/{{ .Watch.ID }}" class="btn btn-outline-secondary btn-sm">
View Watch
</a>
</td>
@ -66,7 +66,7 @@ GoWatch Edit {{ .Watch.Name }}
</div>
<div class="modal-body">
<div>
<form action="/filter/create" method="post">
<form action="{{.urlPrefix}}filter/create" method="post">
<div class="mb-3 m-3 row">
<label for="nameInput" class="col-sm-2 col-form-label">Name:</label>
<div class="col-sm-10 p-2">
@ -136,7 +136,7 @@ GoWatch Edit {{ .Watch.Name }}
</div>
<div class="modal-body">
<div>
<form action="/watch/update" id="saveWatchForm" method="post">
<form action="{{.urlPrefix}}watch/update" id="saveWatchForm" method="post">
<div class="mb-3 m-3 row">
<input type="hidden" id="watch_id" name="watch_id" value="{{ .Watch.ID }}">
<label for="watchNameInput" class="col-sm-2 col-form-label">Name:</label>
@ -148,7 +148,7 @@ GoWatch Edit {{ .Watch.Name }}
</div>
</form>
<button class="btn btn-primary mt-4 float-start" id="saveButtonModal" data-bs-dismiss="modal" id="submitWatchButton">Save</button>
<form action="/watch/delete" method="post">
<form action="{{.urlPrefix}}watch/delete" method="post">
<input type="hidden" name="watch_id" value="{{ .Watch.ID }}">
<button class="btn btn-danger mt-4 float-end" data-bs-dismiss="modal" id="deleteFilterButton">Delete Watch</button>
</form>
@ -166,7 +166,7 @@ GoWatch Edit {{ .Watch.Name }}
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form action="/watch/import/{{ .Watch.ID }}" enctype="multipart/form-data" method="post" class="row g-3">
<form action="{{.urlPrefix}}watch/import/{{ .Watch.ID }}" enctype="multipart/form-data" method="post" class="row g-3">
<div class="mb-3">
<label for="json" class="form-label">Upload JSON to import</label>
<input class="form-control" type="file" id="json" name="json">

View file

@ -13,7 +13,7 @@ GoWatch {{ .Watch.Name }}
<div class="card-body">
<div class="card-title text-center h4">
{{ .Watch.Name }}
<a href="/watch/edit/{{ .Watch.ID }}" class="btn btn-sm btn-success">Edit</a>
<a href="{{.urlPrefix}}watch/edit/{{ .Watch.ID }}" class="btn btn-sm btn-success">Edit</a>
</div>
{{ if not .Watch.CronEntry }}
<h5>No Schedule</h5>

View file

@ -5,5 +5,5 @@
- util.go
- edit.ts
- diagram.ts
- url path support
- config in config?
- refactor project structure