added 'schedules' page, allowing quick enabling/disabling of schedule filters
This commit is contained in:
parent
a9790cd64d
commit
9938af6a3b
4 changed files with 142 additions and 16 deletions
|
@ -3,23 +3,27 @@ package models
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
|
|
||||||
|
"github.com/robfig/cron/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FilterID uint
|
type FilterID uint
|
||||||
type FilterType string
|
type FilterType string
|
||||||
type Filter struct {
|
type Filter struct {
|
||||||
ID FilterID `form:"filter_id" yaml:"filter_id" json:"filter_id"`
|
ID FilterID `form:"filter_id" yaml:"filter_id" json:"filter_id"`
|
||||||
WatchID WatchID `form:"filter_watch_id" gorm:"index" yaml:"filter_watch_id" json:"filter_watch_id" binding:"required"`
|
WatchID WatchID `form:"filter_watch_id" gorm:"index" yaml:"filter_watch_id" json:"filter_watch_id" binding:"required"`
|
||||||
Name string `form:"filter_name" gorm:"index" yaml:"filter_name" json:"filter_name" binding:"required" validate:"min=1"`
|
Name string `form:"filter_name" gorm:"index" yaml:"filter_name" json:"filter_name" binding:"required" validate:"min=1"`
|
||||||
X int `form:"x" yaml:"x" json:"x" validate:"default=0"`
|
X int `form:"x" yaml:"x" json:"x" validate:"default=0"`
|
||||||
Y int `form:"y" yaml:"y" json:"y" validate:"default=0"`
|
Y int `form:"y" yaml:"y" json:"y" validate:"default=0"`
|
||||||
Type FilterType `form:"filter_type" yaml:"filter_type" json:"filter_type" binding:"required" validate:"oneof=url xpath json css replace match substring math store condition cron"`
|
Type FilterType `form:"filter_type" yaml:"filter_type" json:"filter_type" binding:"required" validate:"oneof=url xpath json css replace match substring math store condition cron"`
|
||||||
Var1 string `form:"var1" yaml:"var1" json:"var1" binding:"required"`
|
Var1 string `form:"var1" yaml:"var1" json:"var1" binding:"required"`
|
||||||
Var2 *string `form:"var2" yaml:"var2" json:"var2"`
|
Var2 *string `form:"var2" yaml:"var2" json:"var2"`
|
||||||
Parents []*Filter `gorm:"-:all"`
|
Parents []*Filter `gorm:"-:all"`
|
||||||
Children []*Filter `gorm:"-:all"`
|
Children []*Filter `gorm:"-:all"`
|
||||||
Results []string `gorm:"-:all"`
|
Results []string `gorm:"-:all"`
|
||||||
Logs []string `gorm:"-:all"`
|
Logs []string `gorm:"-:all"`
|
||||||
|
CronEntry *cron.Entry `gorm:"-:all"`
|
||||||
|
Enabled string `gorm:"-:all"` // lazy fix for not being able to pointer deref in golang template....
|
||||||
}
|
}
|
||||||
|
|
||||||
func (filter *Filter) Log(v ...any) {
|
func (filter *Filter) Log(v ...any) {
|
||||||
|
|
|
@ -18,15 +18,12 @@
|
||||||
|
|
||||||
<nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
|
<nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<a class="navbar-brand" href="#">Go Watch</a>
|
<a class="navbar-brand" href="{{.urlPrefix}}">Go Watch</a>
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
<span class="navbar-toggler-icon"></span>
|
<span class="navbar-toggler-icon"></span>
|
||||||
</button>
|
</button>
|
||||||
<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">
|
|
||||||
<a class="nav-link active" aria-current="page" href="{{.urlPrefix}}">Home</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" aria-current="page" href="{{.urlPrefix}}notifiers/view">Notifiers</a>
|
<a class="nav-link" aria-current="page" href="{{.urlPrefix}}notifiers/view">Notifiers</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -36,6 +33,9 @@
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" aria-current="page" href="{{.urlPrefix}}backup/view">Backups</a>
|
<a class="nav-link" aria-current="page" href="{{.urlPrefix}}backup/view">Backups</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" aria-current="page" href="{{.urlPrefix}}schedules/view">Schedules</a>
|
||||||
|
</li>
|
||||||
{{ template "navbar" .}}
|
{{ template "navbar" .}}
|
||||||
</ul>
|
</ul>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
|
|
49
web/templates/schedules.html
Normal file
49
web/templates/schedules.html
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
{{define "title"}}
|
||||||
|
GoWatch
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "content"}}
|
||||||
|
<form action="{{.urlPrefix}}schedules/update" method="POST">
|
||||||
|
<table class="table table-striped table-hover">
|
||||||
|
<thead class="table-dark">
|
||||||
|
<tr>
|
||||||
|
<th>WatchID</th>
|
||||||
|
<th>Watch</th>
|
||||||
|
<th>Schedule</th>
|
||||||
|
<th>Last</th>
|
||||||
|
<th>Next</th>
|
||||||
|
<th>Enabled</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{ range $watch, $scheduleFilters := .watchSchedules }}
|
||||||
|
{{ range $i, $scheduleFilter := $scheduleFilters }}
|
||||||
|
<tr>
|
||||||
|
<td class="h3">{{ $watch.ID }}</td>
|
||||||
|
<td class="h3">{{ $watch.Name }}</td>
|
||||||
|
<td class="h3">{{ $scheduleFilter.Name }}</td>
|
||||||
|
{{ if $scheduleFilter.CronEntry }}
|
||||||
|
<td class="h3">{{ $scheduleFilter.CronEntry.Prev.Format "2006-01-02 15:04:05" }}</td>
|
||||||
|
<td class="h3">{{ $scheduleFilter.CronEntry.Next.Format "2006-01-02 15:04:05" }}</td>
|
||||||
|
{{ else }}
|
||||||
|
<td colspan="2" class="h3">Not scheduled</td>
|
||||||
|
{{ end }}
|
||||||
|
{{ if eq $scheduleFilter.Enabled "yes" }}
|
||||||
|
<td class="h2">
|
||||||
|
<input class="form-check-input" id="schedules_{{ $scheduleFilter.ID }}" type="checkbox" value="{{ $scheduleFilter.ID }}" name="schedules" checked>
|
||||||
|
</td>
|
||||||
|
{{ else }}
|
||||||
|
<td class="h2">
|
||||||
|
<input class="form-check-input" id="schedules_{{ $scheduleFilter.ID }}" type="checkbox" value="{{ $scheduleFilter.ID }}" name="schedules">
|
||||||
|
</td>
|
||||||
|
{{ end}}
|
||||||
|
</tr>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="text-center">
|
||||||
|
<button type="submit" class="btn btn-primary btn-lg">Update</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{{end}}
|
73
web/web.go
73
web/web.go
|
@ -227,6 +227,9 @@ func (web *Web) initRouter() {
|
||||||
gowatch.GET("notifiers/view", web.notifiersView)
|
gowatch.GET("notifiers/view", web.notifiersView)
|
||||||
gowatch.POST("notifiers/test", web.notifiersTest)
|
gowatch.POST("notifiers/test", web.notifiersTest)
|
||||||
|
|
||||||
|
gowatch.GET("schedules/view", web.schedulesView)
|
||||||
|
gowatch.POST("schedules/update", web.schedulesUpdate)
|
||||||
|
|
||||||
gowatch.GET("backup/view", web.backupView)
|
gowatch.GET("backup/view", web.backupView)
|
||||||
gowatch.GET("backup/create", web.backupCreate)
|
gowatch.GET("backup/create", web.backupCreate)
|
||||||
gowatch.POST("backup/test", web.backupTest)
|
gowatch.POST("backup/test", web.backupTest)
|
||||||
|
@ -256,6 +259,8 @@ func (web *Web) initTemplates() {
|
||||||
|
|
||||||
web.templates.Add("notifiersView", template.Must(template.ParseFS(templatesFS, "base.html", "notifiers.html")))
|
web.templates.Add("notifiersView", template.Must(template.ParseFS(templatesFS, "base.html", "notifiers.html")))
|
||||||
|
|
||||||
|
web.templates.Add("schedulesView", template.Must(template.ParseFS(templatesFS, "base.html", "schedules.html")))
|
||||||
|
|
||||||
web.templates.Add("backupView", template.Must(template.ParseFS(templatesFS, "base.html", "backup/view.html")))
|
web.templates.Add("backupView", template.Must(template.ParseFS(templatesFS, "base.html", "backup/view.html")))
|
||||||
web.templates.Add("backupTest", template.Must(template.ParseFS(templatesFS, "base.html", "backup/test.html")))
|
web.templates.Add("backupTest", template.Must(template.ParseFS(templatesFS, "base.html", "backup/test.html")))
|
||||||
web.templates.Add("backupRestore", template.Must(template.ParseFS(templatesFS, "base.html", "backup/restore.html")))
|
web.templates.Add("backupRestore", template.Must(template.ParseFS(templatesFS, "base.html", "backup/restore.html")))
|
||||||
|
@ -513,6 +518,74 @@ func (web *Web) notifiersTest(c *gin.Context) {
|
||||||
c.Redirect(http.StatusSeeOther, web.urlPrefix+"notifiers/view")
|
c.Redirect(http.StatusSeeOther, web.urlPrefix+"notifiers/view")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (web *Web) schedulesView(c *gin.Context) {
|
||||||
|
watches := []Watch{}
|
||||||
|
web.db.Find(&watches)
|
||||||
|
|
||||||
|
watchMap := make(map[WatchID]*Watch, len(watches))
|
||||||
|
for i := 0; i < len(watches); i++ {
|
||||||
|
watchMap[watches[i].ID] = &watches[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
var filters []Filter
|
||||||
|
web.db.Model(&Filter{}).Find(&filters, "type = 'cron'")
|
||||||
|
|
||||||
|
watchSchedules := make(map[*Watch][]*Filter)
|
||||||
|
for i := range filters {
|
||||||
|
filter := &filters[i]
|
||||||
|
entryID, exists := web.cronWatch[filter.ID]
|
||||||
|
|
||||||
|
if exists {
|
||||||
|
entry := web.cron.Entry(entryID)
|
||||||
|
filter.CronEntry = &entry
|
||||||
|
}
|
||||||
|
filter.Enabled = *filter.Var2
|
||||||
|
watch := watchMap[filter.WatchID]
|
||||||
|
watchSchedules[watch] = append(watchSchedules[watch], filter)
|
||||||
|
}
|
||||||
|
c.HTML(http.StatusOK, "schedulesView", gin.H{
|
||||||
|
"watchSchedules": watchSchedules,
|
||||||
|
"urlPrefix": web.urlPrefix,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (web *Web) schedulesUpdate(c *gin.Context) {
|
||||||
|
scheduleStrings := c.PostFormArray("schedules")
|
||||||
|
scheduleIDs := make(map[FilterID]bool)
|
||||||
|
for _, scheduleString := range scheduleStrings {
|
||||||
|
scheduleID, err := strconv.Atoi(scheduleString)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
scheduleIDs[FilterID(scheduleID)] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
var cronFilters []Filter
|
||||||
|
web.db.Model(&Filter{}).Where("type = 'cron'").Find(&cronFilters)
|
||||||
|
for i := range cronFilters {
|
||||||
|
cronFilter := &cronFilters[i]
|
||||||
|
entryID, exist := web.cronWatch[cronFilter.ID]
|
||||||
|
_, checked := scheduleIDs[cronFilter.ID]
|
||||||
|
if exist && !checked {
|
||||||
|
web.cron.Remove(entryID)
|
||||||
|
delete(web.cronWatch, cronFilter.ID)
|
||||||
|
} else if !exist && checked {
|
||||||
|
yes := "yes"
|
||||||
|
cronFilter.Var2 = &yes
|
||||||
|
web.addCronJobIfCronFilter(cronFilter, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(scheduleStrings) > 0 {
|
||||||
|
web.db.Model(&Filter{}).Where("ID IN ?", scheduleStrings).Update("Var2", "yes")
|
||||||
|
web.db.Model(&Filter{}).Where("ID NOT IN ?", scheduleStrings).Update("Var2", "no")
|
||||||
|
} else {
|
||||||
|
web.db.Model(&Filter{}).Where("TRUE").Update("Var2", "no")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Redirect(http.StatusSeeOther, web.urlPrefix+"schedules/view")
|
||||||
|
}
|
||||||
|
|
||||||
// watchCreate (/watch/create) allows user to create a new watch
|
// watchCreate (/watch/create) allows user to create a new watch
|
||||||
// A name and an optional template can be picked
|
// A name and an optional template can be picked
|
||||||
func (web *Web) watchCreate(c *gin.Context) {
|
func (web *Web) watchCreate(c *gin.Context) {
|
||||||
|
|
Loading…
Add table
Reference in a new issue