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) - [Pruning](#pruning)
- [Proxy](#proxy) - [Proxy](#proxy)
- [Proxy Pools](#proxy-pools) - [Proxy Pools](#proxy-pools)
- [Reverse Proxy](#reverse-proxy)
- [Browserless](#browserless) - [Browserless](#browserless)
- [Authentication](#Authentication) - [Authentication](#Authentication)
- [Filters](#filters) - [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). 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 ## Browserless
Some websites (Amazon for example) don't send all content on the first request, it's added later through javascript. 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 url: http://your.browserless:1234
gin: gin:
debug: false debug: false
urlprefix: "/"
schedule: schedule:
delay: "5s" delay: "5s"

165
main.go
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -4,9 +4,13 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/static/bootstrap.5.2.0.min.css" rel="stylesheet"> <link href="{{.urlPrefix}}static/bootstrap.5.2.0.min.css" rel="stylesheet">
<link href="/static/style.css" rel="stylesheet"> <link href="{{.urlPrefix}}static/style.css" rel="stylesheet">
<script>
function getURLPrefix() {
return "{{.urlPrefix}}";
}
</script>
<title>{{ template "title" .}}</title> <title>{{ template "title" .}}</title>
{{ template "head" .}} {{ template "head" .}}
</head> </head>
@ -21,21 +25,21 @@
<div class="collapse navbar-collapse" id="navbarCollapse"> <div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav me-auto mb-2 mb-md-0"> <ul class="navbar-nav me-auto mb-2 mb-md-0">
<li class="nav-item"> <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>
<li class="nav-item"> <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>
<li class="nav-item"> <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>
<li class="nav-item"> <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> </li>
{{ template "navbar" .}} {{ template "navbar" .}}
</ul> </ul>
<div class="d-flex"> <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> </div>
</div> </div>
@ -64,8 +68,8 @@
<span class="text-muted">Broodjeaap 2023</span> <span class="text-muted">Broodjeaap 2023</span>
</div> </div>
</footer> </footer>
<script src="/static/bootstrap.5.2.0.bundle.min.js"></script> <script src="{{.urlPrefix}}static/bootstrap.5.2.0.bundle.min.js"></script>
<script src="/static/script.js"></script> <script src="{{.urlPrefix}}static/script.js"></script>
{{ template "scripts" .}} {{ template "scripts" .}}
</body> </body>
</html> </html>

View file

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

View file

@ -28,7 +28,7 @@ GoWatch
</thead> </thead>
<tbody> <tbody>
{{ range .watches }} {{ 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> <td class="h3">{{ .Name }}</td>
{{ if .CronEntry }} {{ if .CronEntry }}
<td class="h3">{{ .CronEntry.Prev.Format "2006-01-02 15:04:05" }}</td> <td class="h3">{{ .CronEntry.Prev.Format "2006-01-02 15:04:05" }}</td>
@ -41,10 +41,10 @@ GoWatch
{{ .LastValue }} {{ .LastValue }}
</td> </td>
<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>
<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="hidden" name="watch_id" value="{{ .ID }}">
<input type="submit" class="btn btn-danger" value="Delete"> <input type="submit" class="btn btn-danger" value="Delete">
</form> </form>

View file

@ -11,11 +11,11 @@ GoWatch
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{{ range $name, $notifier := . }} {{ range $name, $notifier := .notifiers }}
<tr> <tr>
<td class="h3">{{ $name }}</td> <td class="h3">{{ $name }}</td>
<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="hidden" name="notifier_name" value="{{ $name }}">
<input type="submit" class="btn btn-primary" value="Test"> <input type="submit" class="btn btn-primary" value="Test">
</form> </form>

View file

@ -3,12 +3,12 @@ GoWatch Create
{{end}} {{end}}
{{define "head"}} {{define "head"}}
<script src="/static/create.js"></script> <script src="{{.urlPrefix}}static/create.js"></script>
{{ end }} {{ end }}
{{define "content"}} {{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="container">
<div class="row g-3 justify-content-center"> <div class="row g-3 justify-content-center">
<div class="col-auto"> <div class="col-auto">

View file

@ -1,6 +1,6 @@
{{define "head"}} {{define "head"}}
<script src="/static/diagram.js"></script> <script src="{{.urlPrefix}}static/diagram.js"></script>
<script src="/static/edit.js"></script> <script src="{{.urlPrefix}}static/edit.js"></script>
{{ end }} {{ end }}
{{define "content"}} {{define "content"}}
<div class="canvas_parent"> <div class="canvas_parent">
@ -34,7 +34,7 @@ GoWatch Edit {{ .Watch.Name }}
</button> </button>
</td> </td>
<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 Export Watch
</a> </a>
</td> </td>
@ -49,7 +49,7 @@ GoWatch Edit {{ .Watch.Name }}
</button> </button>
</td> </td>
<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 View Watch
</a> </a>
</td> </td>
@ -66,7 +66,7 @@ GoWatch Edit {{ .Watch.Name }}
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div> <div>
<form action="/filter/create" method="post"> <form action="{{.urlPrefix}}filter/create" method="post">
<div class="mb-3 m-3 row"> <div class="mb-3 m-3 row">
<label for="nameInput" class="col-sm-2 col-form-label">Name:</label> <label for="nameInput" class="col-sm-2 col-form-label">Name:</label>
<div class="col-sm-10 p-2"> <div class="col-sm-10 p-2">
@ -136,7 +136,7 @@ GoWatch Edit {{ .Watch.Name }}
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div> <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"> <div class="mb-3 m-3 row">
<input type="hidden" id="watch_id" name="watch_id" value="{{ .Watch.ID }}"> <input type="hidden" id="watch_id" name="watch_id" value="{{ .Watch.ID }}">
<label for="watchNameInput" class="col-sm-2 col-form-label">Name:</label> <label for="watchNameInput" class="col-sm-2 col-form-label">Name:</label>
@ -148,7 +148,7 @@ GoWatch Edit {{ .Watch.Name }}
</div> </div>
</form> </form>
<button class="btn btn-primary mt-4 float-start" id="saveButtonModal" data-bs-dismiss="modal" id="submitWatchButton">Save</button> <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 }}"> <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> <button class="btn btn-danger mt-4 float-end" data-bs-dismiss="modal" id="deleteFilterButton">Delete Watch</button>
</form> </form>
@ -166,7 +166,7 @@ GoWatch Edit {{ .Watch.Name }}
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <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"> <div class="mb-3">
<label for="json" class="form-label">Upload JSON to import</label> <label for="json" class="form-label">Upload JSON to import</label>
<input class="form-control" type="file" id="json" name="json"> <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-body">
<div class="card-title text-center h4"> <div class="card-title text-center h4">
{{ .Watch.Name }} {{ .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> </div>
{{ if not .Watch.CronEntry }} {{ if not .Watch.CronEntry }}
<h5>No Schedule</h5> <h5>No Schedule</h5>

View file

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