From 1c00dfabc94a294438ca15c773ac3f06279c3938 Mon Sep 17 00:00:00 2001 From: nxshock Date: Wed, 30 Mar 2022 21:06:55 +0500 Subject: [PATCH] New updates * Rework job status * Group WebUI files * New WebUI details page --- httpserver.go | 40 +++++-- index.htm | 225 ------------------------------------- job.go | 9 +- status.go | 10 ++ template.go | 10 +- webui/details.htm | 78 +++++++++++++ font.ttf => webui/font.ttf | Bin webui/index.htm | 57 ++++++++++ webui/style.css | 203 +++++++++++++++++++++++++++++++++ 9 files changed, 391 insertions(+), 241 deletions(-) delete mode 100644 index.htm create mode 100644 status.go create mode 100644 webui/details.htm rename font.ttf => webui/font.ttf (100%) create mode 100644 webui/index.htm create mode 100644 webui/style.css diff --git a/httpserver.go b/httpserver.go index e5f658e..4f4e581 100644 --- a/httpserver.go +++ b/httpserver.go @@ -4,6 +4,7 @@ import ( "bytes" _ "embed" "fmt" + "io/fs" "net" "net/http" "os" @@ -12,25 +13,27 @@ import ( log "github.com/sirupsen/logrus" ) -//go:embed font.ttf -var font []byte - func httpServer(listenAddress string) { if listenAddress == "none" { return } http.HandleFunc("/", handler) - http.HandleFunc("/font.ttf", handleFont) http.HandleFunc("/reloadJobs", handleReloadJobs) http.HandleFunc("/shutdown", handleShutdown) http.HandleFunc("/start", handleForceStart) + http.HandleFunc("/details", handleDetails) log.WithField("job", "http_server").Fatal(http.ListenAndServe(listenAddress, nil)) } func handler(w http.ResponseWriter, r *http.Request) { if r.RequestURI != "/" { - http.Error(w, "", http.StatusNotFound) + fs, err := fs.Sub(siteFS, "webui") + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + http.FileServer(http.FS(fs)).ServeHTTP(w, r) return } @@ -43,7 +46,7 @@ func handler(w http.ResponseWriter, r *http.Request) { job.NextLaunch = jobEntry.Next.Format(config.TimeFormat) jobs = append(jobs, job) } - indexTemplate.ExecuteTemplate(buf, "index", jobs) + templates.ExecuteTemplate(buf, "index.htm", jobs) globalMutex.RUnlock() buf.WriteTo(w) @@ -108,8 +111,25 @@ func handleReloadJobs(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/", http.StatusTemporaryRedirect) } -func handleFont(w http.ResponseWriter, r *http.Request) { - w.Header().Add("Content-Type", "font/ttf") - w.Header().Add("Cache-Control", "public") // TODO - w.Write(font) +func handleDetails(w http.ResponseWriter, r *http.Request) { + jobName := r.FormValue("jobName") + if jobName == "" { + http.Error(w, "job name is not specified", http.StatusBadRequest) + return + } + + jobEntries := c.Entries() + + for _, jobEntry := range jobEntries { + job := jobEntry.Job.(*Job) + if job.Name == jobName { + err := templates.ExecuteTemplate(w, "details.htm", job) + if err != nil { + fmt.Println(err) + } + return + } + } + + http.Error(w, fmt.Sprintf("there is no job with name %s", jobName), http.StatusBadRequest) } diff --git a/index.htm b/index.htm deleted file mode 100644 index 904f18e..0000000 --- a/index.htm +++ /dev/null @@ -1,225 +0,0 @@ - - - - - - - gron - - - - -
- -

Job list

- - - - - - - - - - - - - {{range .}} - - - - - - - - - - {{end}} -
NameDescriptionCronStatusStart timeFinish timeDurationNext launchLast error
-
- {{.Name}} -
{{.JobConfig.Description}} -
{{.JobConfig.Cron}}
-
{{if gt .CurrentRunningCount 1}}⯁ running {{.CurrentRunningCount}} jobs{{else}}{{if .CurrentRunningCount}}⯈ running{{else}}⯀ inactive{{end}}{{end}}{{.LastStartTime}}{{.LastEndTime}}{{.LastExecutionDuration}}{{.NextLaunch}}{{.LastError}}
-
- - - diff --git a/job.go b/job.go index faa8e3a..81317ce 100644 --- a/job.go +++ b/job.go @@ -30,6 +30,7 @@ type Job struct { JobConfig JobConfig // Fields for stats + Status Status CurrentRunningCount int LastStartTime string LastEndTime string @@ -50,6 +51,7 @@ func readJob(filePath string) (*Job, error) { job := &Job{ Name: strings.TrimSuffix(filepath.Base(filePath), filepath.Ext(filePath)), + Status: Inactive, JobConfig: jobConfig} return job, nil @@ -100,6 +102,7 @@ func (j *Job) Run() { globalMutex.Lock() j.CurrentRunningCount++ + j.Status = Running j.LastStartTime = startTime.Format(config.TimeFormat) globalMutex.Unlock() @@ -111,12 +114,14 @@ func (j *Job) Run() { err := cmd.Run() if err != nil { + j.Status = Error log.Error(err.Error()) globalMutex.Lock() j.LastError = err.Error() globalMutex.Unlock() } else { + j.Status = Inactive globalMutex.Lock() j.LastError = "" globalMutex.Unlock() @@ -141,7 +146,9 @@ func (j *Job) Run() { if i == 0 { log.Printf("Job failed, restarting in %d seconds.", j.JobConfig.RestartSec) - } else if i+1 < j.JobConfig.NumberOfRestartAttemts { + j.Status = Restarting + } else if i < j.JobConfig.NumberOfRestartAttemts { + j.Status = Restarting log.Printf("Retry attempt №%d of %d failed, restarting in %d seconds.", i, j.JobConfig.NumberOfRestartAttemts, j.JobConfig.RestartSec) } else { log.Printf("Retry attempt №%d of %d failed.", i, j.JobConfig.NumberOfRestartAttemts) diff --git a/status.go b/status.go new file mode 100644 index 0000000..2157c29 --- /dev/null +++ b/status.go @@ -0,0 +1,10 @@ +package main + +type Status int + +const ( + Inactive Status = iota + Running + Error + Restarting +) diff --git a/template.go b/template.go index 2248b62..c009287 100644 --- a/template.go +++ b/template.go @@ -1,20 +1,20 @@ package main import ( - _ "embed" + "embed" "html/template" log "github.com/sirupsen/logrus" ) -//go:embed index.htm -var indexTemplateStr string +//go:embed webui/* +var siteFS embed.FS -var indexTemplate *template.Template +var templates *template.Template func initTemplate() { var err error - indexTemplate, err = template.New("index").Parse(indexTemplateStr) // TODO: optimize + templates, err = template.ParseFS(siteFS, "webui/*.htm") if err != nil { log.Fatalln("init template error:", err) } diff --git a/webui/details.htm b/webui/details.htm new file mode 100644 index 0000000..6e39272 --- /dev/null +++ b/webui/details.htm @@ -0,0 +1,78 @@ + + + + + + + gron + + + + +
+ ← Back +

{{.Name}}

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Description{{.JobConfig.Description}}
Cron
{{.JobConfig.Cron}}
Command
{{.JobConfig.Command}}
Status{{if eq .Status 0}}⯀ inactive{{end}}{{if eq .Status 1}}⯈ running{{end}}{{if eq .Status 2}}⯁ error{{end}}{{if eq .Status 3}}⟳ restarting{{end}}
+

Stats

+
Start time{{.LastStartTime}}
Finish time{{.LastEndTime}}
Duration{{.LastExecutionDuration}}
Last error
{{.LastError}}
Next launch{{.NextLaunch}}
+

On error action

+
Restart rule{{if eq .JobConfig.RestartRule 0}}no restart{{end}}{{if eq .JobConfig.RestartRule 1}}restart on error{{end}}
Number of restart attempts{{.JobConfig.NumberOfRestartAttemts}}
Restart delay{{if eq .JobConfig.RestartSec 0}}none{{else}}{{.JobConfig.RestartSec}} sec{{end}}
+
+ + + \ No newline at end of file diff --git a/font.ttf b/webui/font.ttf similarity index 100% rename from font.ttf rename to webui/font.ttf diff --git a/webui/index.htm b/webui/index.htm new file mode 100644 index 0000000..9d7163c --- /dev/null +++ b/webui/index.htm @@ -0,0 +1,57 @@ + + + + + + + gron + + + + +
+ +

Job list

+ + + + + + + + + + + + + {{range .}} + + + + + + + + + + + {{end}} +
NameDescriptionCronStatusStart timeFinish timeDurationNext launchDetails
+
+ {{.Name}} +
{{.JobConfig.Description}} +
{{.JobConfig.Cron}}
+
{{if eq .Status 0}}⯀ inactive{{end}}{{if eq .Status 1}}⯈ running{{end}}{{if eq .Status 2}}⯁ error{{end}}{{if eq .Status 3}}⟳ restarting{{end}}{{.LastStartTime}}{{.LastEndTime}}{{.LastExecutionDuration}}{{.NextLaunch}}open
+
+ + + \ No newline at end of file diff --git a/webui/style.css b/webui/style.css new file mode 100644 index 0000000..4107a9f --- /dev/null +++ b/webui/style.css @@ -0,0 +1,203 @@ +/* Based on base16 architecture for building themes + https://github.com/chriskempson/base16 */ + + :root { + /* Default Background */ + --base00: #282c34; + /* Lighter Background */ + --base01: #353b45; + /* Selection Background */ + --base02: #3e4451; + /* Comments, Invisibles, Line Highlighting */ + --base03: #545862; + /* Dark Foreground (Used for status bars) */ + --base04: #565c64; + /* Default Foreground, Caret, Delimiters, Operators */ + --base05: #abb2bf; + /* Light Foreground (Not often used) */ + --base06: #b6bdca; + /* Light Background (Not often used) */ + --base07: #c8ccd4; + /* Red */ + --base08: #e06c75; + /* Orange */ + --base09: #d19a66; + /* Yellow */ + --base0A: #e5c07b; + /* Green */ + --base0B: #98c379; + /* Aqua */ + --base0C: #56b6c2; + /* Blue */ + --base0D: #61afef; + /* Purple */ + --base0E: #c678dd; + /* Dark red */ + --base0F: #be5046; +} + +@font-face { + font-family: Roboto; + src: url('/font.ttf'); +} + +* { + font-family: Roboto; + font-size: 14px; + color: var(--base05); + margin: 0; + padding: 0; +} + +html { + height: 100% +} + +body { + background-color: var(--base00); + display: flex; + justify-content: space-between; + flex-wrap: nowrap; + flex-direction: column; + height: 100%; +} + +h1 { + margin-top: 1em; + margin-bottom: 0.5em; + font-size: 150%; +} + +h2 { + margin-top: 1em; + margin-bottom: 0.5em; + font-size: 125%; +} + +h1, +h2 { + color: var(--base0D); + font-weight: bold; +} + +main { + flex-grow: 1; + padding-left: 1em; + padding-right: 1em; +} + +a { + text-decoration: none; +} + +table { + border-collapse: collapse; +} + +th { + background-color: var(--base01); +} + +td, +th { + border: 1px solid var(--base01); + padding: 0.25em; +} + +table button { + width: 100%; + color: #fff; + background-color: var(--base01); + border: 0; + cursor: pointer; + padding: 0.25em; +} + +form { + display: none; +} + +pre { + font-family: "Consolas"; +} + +.no-padding { + padding: 0; +} + +.centered { + text-align: center; +} + +.smaller { + font-size: 80%; +} + +.nowrap { + white-space: nowrap; +} + +.errorbg { + background-color: var(--base08); +} + +.runningbg { + background-color: var(--base0B); +} + +.dropbtn { + background-color: var(--base01); + color: var(--base05); + padding: 0.5em; + cursor: pointer; + border: 0; +} + +.dropdown { + position: relative; + display: inline-block; + float: right; +} + +.dropdown-content { + display: none; + position: absolute; + right: 0; + background-color: var(--base01); + z-index: 1; +} + +.dropdown-content a { + color: var(--base05); + padding: 0.5em; + text-decoration: none; + display: block; + white-space: nowrap; +} + +.dropdown:hover .dropdown-content { + display: block; +} + +.green { + color: var(--base0B); +} + +.orange { + color: var(--base09); +} + +.red { + color: var(--base08); +} + +table.stats td { + border-top: 1px solid var(--base01); + border-bottom: 1px solid var(--base01); + border-left: 0; + border-right: 0; +} + +table.stats td:first-child { + font-weight: bold; +} \ No newline at end of file