Goit

Simple and lightweight Git web server
git clone http://git.omkov.net/Goit
Log | Tree | Refs | README | Download

AuthorJakob Wakeling <[email protected]>
Date2023-12-29 02:18:46
Commit594ec417f426c15db7124a13480e9792dacd0205
Parentd737f58666affab00577c1433ca7d04fd54d9ee2

Add admin cron page and log last job time

Diffstat

A res/admin/cron.html | 30 ++++++++++++++++++++++++++++++
M res/admin/header.html | 1 +
M res/res.go | 3 +++
M res/style.css | 1 +
A src/admin/cron.go | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
M src/admin/repos.go | 2 +-
M src/cron/cron.go | 48 ++++++++++++++++++++++++++++++------------------
M src/cron/schedule.go | 19 +++++++++++++++++++
M src/goit/http.go | 1 +
M src/main.go | 1 +

10 files changed, 145 insertions, 19 deletions

diff --git a/res/admin/cron.html b/res/admin/cron.html
new file mode 100644
index 0000000..a14243d
--- /dev/null
+++ b/res/admin/cron.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<head lang="en-GB">{{template "base/head" .}}</head>
+<body>
+	<header>{{template "admin/header" .}}</header><hr>
+	<main>
+		<table class="highlight-row">
+			<thead>
+				<tr>
+					<td><b>ID</b></td>
+					<td><b>Repository</b></td>
+					<td><b>Schedule</b></td>
+					<td><b>Next</b></td>
+					<td><b>Last</b></td>
+				</tr>
+			</thead>
+			<tbody>
+				{{range .Jobs}}
+				<tr>
+					<td>{{.Id}}</td>
+					<td><a href="/{{.Repo}}">{{.Repo}}</a></td>
+					<td>{{.Schedule}}</td>
+					<td>{{.Next}}</td>
+					<td>{{.Last}}</td>
+				</tr>
+				{{end}}
+			</tbody>
+		</table><hr>
+		<span>Schedule format is month, day, weekday, hour, minute, second, where an asterisk represents "any".</span>
+	</main>
+</body>
diff --git a/res/admin/header.html b/res/admin/header.html
index dcf3bf2..1e3c660 100644
--- a/res/admin/header.html
+++ b/res/admin/header.html
@@ -7,6 +7,7 @@
 		<a href="/admin/status">Status</a>
 		| <a href="/admin/users">Users</a>
 		| <a href="/admin/repos">Repositories</a>
+		| <a href="/admin/cron">Cron</a>
 		| <a href="/admin/user/create">Create User</a>
 	</td></tr>
 </table>
diff --git a/res/res.go b/res/res.go
index f087216..af125bd 100644
--- a/res/res.go
+++ b/res/res.go
@@ -34,6 +34,9 @@ var AdminRepos string
 //go:embed admin/repo_edit.html
 var AdminRepoEdit string
 
+//go:embed admin/cron.html
+var AdminCron string
+
 //go:embed user/header.html
 var UserHeader string
 
diff --git a/res/style.css b/res/style.css
index ddf0589..5417322 100644
--- a/res/style.css
+++ b/res/style.css
@@ -5,6 +5,7 @@ a { color: #FF7E00; text-decoration: none; }
 a:hover { text-decoration: underline; }
 h1, h2 { font-size: 1em; margin: 0; }
 hr { border: 0; height: 1rem; margin: 0; }
+main > span { padding: 0 0.4rem; }
 
 footer { padding: 0.4rem 0.4rem 1rem; }
 
diff --git a/src/admin/cron.go b/src/admin/cron.go
new file mode 100644
index 0000000..ea12693
--- /dev/null
+++ b/src/admin/cron.go
@@ -0,0 +1,58 @@
+// Copyright (C) 2023, Jakob Wakeling
+// All rights reserved.
+
+package admin
+
+import (
+	"fmt"
+	"log"
+	"net/http"
+	"time"
+
+	"github.com/Jamozed/Goit/src/goit"
+	"github.com/Jamozed/Goit/src/util"
+)
+
+func HandleCron(w http.ResponseWriter, r *http.Request) {
+	auth, user, err := goit.Auth(w, r, true)
+	if err != nil {
+		log.Println("[/admin/cron]", err.Error())
+		goit.HttpError(w, http.StatusInternalServerError)
+		return
+	}
+
+	if !auth || !user.IsAdmin {
+		goit.HttpError(w, http.StatusNotFound)
+		return
+	}
+
+	type row struct{ Id, Repo, Schedule, Next, Last string }
+	data := struct {
+		Title string
+		Jobs  []row
+	}{Title: "Admin - Cron"}
+
+	for _, job := range goit.Cron.Jobs() {
+		repo := &goit.Repo{}
+
+		if job.Rid != -1 {
+			if r, err := goit.GetRepo(job.Rid); err != nil {
+				log.Println("[/admin/cron]", err.Error())
+			} else if r != nil {
+				repo = r
+			}
+		}
+
+		data.Jobs = append(data.Jobs, row{
+			Id:       fmt.Sprint(job.Id),
+			Repo:     repo.Name,
+			Schedule: job.Schedule.String(),
+			Next:     job.Next.String(),
+			Last:     util.If(job.Last == time.Time{}, "never", job.Last.String()),
+		})
+	}
+
+	if err := goit.Tmpl.ExecuteTemplate(w, "admin/cron", data); err != nil {
+		log.Println("[/admin/cron]", err.Error())
+	}
+}
diff --git a/src/admin/repos.go b/src/admin/repos.go
index 80f557c..826d959 100644
--- a/src/admin/repos.go
+++ b/src/admin/repos.go
@@ -22,7 +22,7 @@ import (
 func HandleRepos(w http.ResponseWriter, r *http.Request) {
 	auth, user, err := goit.Auth(w, r, true)
 	if err != nil {
-		log.Println("[admin/users]", err.Error())
+		log.Println("[admin/repos]", err.Error())
 		goit.HttpError(w, http.StatusInternalServerError)
 		return
 	}
diff --git a/src/cron/cron.go b/src/cron/cron.go
index 3fdf619..6357732 100644
--- a/src/cron/cron.go
+++ b/src/cron/cron.go
@@ -24,11 +24,11 @@ type Cron struct {
 }
 
 type Job struct {
-	id       uint64
-	rid      int64
-	schedule Schedule
-	next     time.Time
-	fn       func()
+	Id         uint64
+	Rid        int64
+	Schedule   Schedule
+	Next, Last time.Time
+	fn         func()
 }
 
 const maxDuration time.Duration = 1<<63 - 1
@@ -52,7 +52,7 @@ func (c *Cron) Start() {
 	}
 
 	for _, job := range c.jobs {
-		job.next = job.schedule.Next(time.Now().UTC())
+		job.Next = job.Schedule.Next(time.Now().UTC())
 	}
 
 	go func() {
@@ -65,7 +65,7 @@ func (c *Cron) Start() {
 			if len(c.jobs) == 0 {
 				timer = time.NewTimer(maxDuration)
 			} else {
-				timer = time.NewTimer(c.jobs[0].next.Sub(time.Now().UTC()))
+				timer = time.NewTimer(c.jobs[0].Next.Sub(time.Now().UTC()))
 			}
 
 			c.mutex.Unlock()
@@ -81,12 +81,12 @@ func (c *Cron) Start() {
 
 				tmp := c.jobs[:0]
 				for _, job := range c.jobs {
-					if job.next.After(now) || job.next.IsZero() {
+					if job.Next.After(now) || job.Next.IsZero() {
 						tmp = append(tmp, job)
 						continue
 					}
 
-					log.Println("[cron] running job", job.id, job.rid)
+					log.Println("[cron] running job", job.Id, job.Rid)
 
 					j := job
 					c.waiter.Add(1)
@@ -95,8 +95,9 @@ func (c *Cron) Start() {
 						j.fn()
 					}()
 
-					if !job.schedule.IsImmediate() {
-						job.next = job.schedule.Next(now)
+					if !job.Schedule.IsImmediate() {
+						job.Next = job.Schedule.Next(now)
+						job.Last = now
 						tmp = append(tmp, job)
 					}
 				}
@@ -153,10 +154,21 @@ func (c *Cron) _update() {
 
 	now := time.Now().UTC()
 	slices.SortFunc(c.jobs, func(a, b Job) int {
-		return a.schedule.Next(now).Compare(b.schedule.Next(now))
+		return a.Schedule.Next(now).Compare(b.Schedule.Next(now))
 	})
 }
 
+func (c *Cron) Jobs() []Job {
+	c.mutex.Lock()
+	util.Debugln("[cron.Jobs] Cron mutex lock")
+	defer c.mutex.Unlock()
+	defer util.Debugln("[cron.Jobs] Cron mutex unlock")
+
+	jobs := make([]Job, len(c.jobs))
+	copy(jobs, c.jobs)
+	return jobs
+}
+
 func (c *Cron) Add(rid int64, schedule Schedule, fn func()) uint64 {
 	c.mutex.Lock()
 	util.Debugln("[cron.Add] Cron mutex lock")
@@ -165,12 +177,12 @@ func (c *Cron) Add(rid int64, schedule Schedule, fn func()) uint64 {
 
 	c.lastId += 1
 
-	job := Job{id: c.lastId, rid: rid, schedule: schedule, fn: fn}
-	job.next = job.schedule.Next(time.Now().UTC())
+	job := Job{Id: c.lastId, Rid: rid, Schedule: schedule, fn: fn}
+	job.Next = job.Schedule.Next(time.Now().UTC())
 	c.jobs = append(c.jobs, job)
 
-	log.Println("[cron] added job", job.id, "for", job.rid)
-	return job.id
+	log.Println("[cron] added job", job.Id, "for", job.Rid)
+	return job.Id
 }
 
 func (c *Cron) RemoveFor(rid int64) {
@@ -181,10 +193,10 @@ func (c *Cron) RemoveFor(rid int64) {
 
 	tmp := c.jobs[:0]
 	for _, job := range c.jobs {
-		if job.rid != rid {
+		if job.Rid != rid {
 			tmp = append(tmp, job)
 		} else {
-			log.Println("[cron] removing job", job.id, "for", job.rid)
+			log.Println("[cron] removing job", job.Id, "for", job.Rid)
 		}
 	}
 
diff --git a/src/cron/schedule.go b/src/cron/schedule.go
index 4a83656..be7d4e9 100644
--- a/src/cron/schedule.go
+++ b/src/cron/schedule.go
@@ -4,7 +4,10 @@
 package cron
 
 import (
+	"fmt"
 	"time"
+
+	"github.com/Jamozed/Goit/src/util"
 )
 
 type Schedule struct{ Month, Day, Weekday, Hour, Minute, Second int64 }
@@ -96,3 +99,19 @@ wrap:
 func (s Schedule) IsImmediate() bool {
 	return s == Immediate
 }
+
+func (s Schedule) String() string {
+	if s.IsImmediate() {
+		return "immediate"
+	}
+
+	return fmt.Sprintf(
+		"%s %s %s %s %s %s",
+		util.If(s.Month == -1, "*", fmt.Sprint(s.Month)),
+		util.If(s.Day == -1, "*", fmt.Sprint(s.Day)),
+		util.If(s.Weekday == -1, "*", fmt.Sprint(s.Weekday)),
+		util.If(s.Hour == -1, "*", fmt.Sprint(s.Hour)),
+		util.If(s.Minute == -1, "*", fmt.Sprint(s.Minute)),
+		util.If(s.Second == -1, "*", fmt.Sprint(s.Second)),
+	)
+}
diff --git a/src/goit/http.go b/src/goit/http.go
index 3fc333b..1fea509 100644
--- a/src/goit/http.go
+++ b/src/goit/http.go
@@ -25,6 +25,7 @@ func init() {
 	template.Must(Tmpl.New("admin/user/edit").Parse(res.AdminUserEdit))
 	template.Must(Tmpl.New("admin/repos").Parse(res.AdminRepos))
 	template.Must(Tmpl.New("admin/repo/edit").Parse(res.AdminRepoEdit))
+	template.Must(Tmpl.New("admin/cron").Parse(res.AdminCron))
 
 	template.Must(Tmpl.New("user/header").Parse(res.UserHeader))
 	template.Must(Tmpl.New("user/login").Parse(res.UserLogin))
diff --git a/src/main.go b/src/main.go
index 4a88489..229ec0b 100644
--- a/src/main.go
+++ b/src/main.go
@@ -125,6 +125,7 @@ func main() {
 		r.Get("/admin/repos", admin.HandleRepos)
 		r.Get("/admin/repo/edit", admin.HandleRepoEdit)
 		r.Post("/admin/repo/edit", admin.HandleRepoEdit)
+		r.Get("/admin/cron", admin.HandleCron)
 
 		r.Get("/static/style.css", handleStyle)
 		r.Get("/static/favicon.png", handleFavicon)