mirror of
https://github.com/nxshock/gron.git
synced 2024-11-27 03:41:00 +05:00
New updates
* Rework job status * Group WebUI files * New WebUI details page
This commit is contained in:
parent
236507c5c0
commit
1c00dfabc9
@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@ -12,25 +13,27 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed font.ttf
|
|
||||||
var font []byte
|
|
||||||
|
|
||||||
func httpServer(listenAddress string) {
|
func httpServer(listenAddress string) {
|
||||||
if listenAddress == "none" {
|
if listenAddress == "none" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
http.HandleFunc("/", handler)
|
http.HandleFunc("/", handler)
|
||||||
http.HandleFunc("/font.ttf", handleFont)
|
|
||||||
http.HandleFunc("/reloadJobs", handleReloadJobs)
|
http.HandleFunc("/reloadJobs", handleReloadJobs)
|
||||||
http.HandleFunc("/shutdown", handleShutdown)
|
http.HandleFunc("/shutdown", handleShutdown)
|
||||||
http.HandleFunc("/start", handleForceStart)
|
http.HandleFunc("/start", handleForceStart)
|
||||||
|
http.HandleFunc("/details", handleDetails)
|
||||||
log.WithField("job", "http_server").Fatal(http.ListenAndServe(listenAddress, nil))
|
log.WithField("job", "http_server").Fatal(http.ListenAndServe(listenAddress, nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func handler(w http.ResponseWriter, r *http.Request) {
|
func handler(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.RequestURI != "/" {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,7 +46,7 @@ func handler(w http.ResponseWriter, r *http.Request) {
|
|||||||
job.NextLaunch = jobEntry.Next.Format(config.TimeFormat)
|
job.NextLaunch = jobEntry.Next.Format(config.TimeFormat)
|
||||||
jobs = append(jobs, job)
|
jobs = append(jobs, job)
|
||||||
}
|
}
|
||||||
indexTemplate.ExecuteTemplate(buf, "index", jobs)
|
templates.ExecuteTemplate(buf, "index.htm", jobs)
|
||||||
globalMutex.RUnlock()
|
globalMutex.RUnlock()
|
||||||
|
|
||||||
buf.WriteTo(w)
|
buf.WriteTo(w)
|
||||||
@ -108,8 +111,25 @@ func handleReloadJobs(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleFont(w http.ResponseWriter, r *http.Request) {
|
func handleDetails(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Add("Content-Type", "font/ttf")
|
jobName := r.FormValue("jobName")
|
||||||
w.Header().Add("Cache-Control", "public") // TODO
|
if jobName == "" {
|
||||||
w.Write(font)
|
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)
|
||||||
}
|
}
|
||||||
|
225
index.htm
225
index.htm
@ -1,225 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
|
||||||
<title>gron</title>
|
|
||||||
<style>
|
|
||||||
/* 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;
|
|
||||||
font-size: 150%;
|
|
||||||
margin-bottom: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
main {
|
|
||||||
flex-grow: 1;
|
|
||||||
padding-left: 1em;
|
|
||||||
padding-right: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
background-color: var(--base01);
|
|
||||||
}
|
|
||||||
|
|
||||||
td,
|
|
||||||
th {
|
|
||||||
border: 1px solid var(--base03);
|
|
||||||
padding: 0.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
td:first-child {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
table button {
|
|
||||||
width: 100%;
|
|
||||||
color: #fff;
|
|
||||||
background-color: var(--base01);
|
|
||||||
border: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 0.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
form {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
font-family: "Consolas";
|
|
||||||
}
|
|
||||||
|
|
||||||
.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);
|
|
||||||
}
|
|
||||||
|
|
||||||
.red {
|
|
||||||
color: var(--base08);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<main>
|
|
||||||
<div class="dropdown">
|
|
||||||
<button class="dropbtn">☰ Menu</button>
|
|
||||||
<div class="dropdown-content">
|
|
||||||
<a href="/reloadJobs">⟳ Reload jobs</a>
|
|
||||||
<a>
|
|
||||||
<hr>
|
|
||||||
</a>
|
|
||||||
<a href="/shutdown">⏻ Shutdown</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<h1>Job list</h1>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<th>Name</th>
|
|
||||||
<th>Description</th>
|
|
||||||
<th>Cron</th>
|
|
||||||
<th>Status</th>
|
|
||||||
<th>Start time</th>
|
|
||||||
<th>Finish time</th>
|
|
||||||
<th>Duration</th>
|
|
||||||
<th>Next launch</th>
|
|
||||||
<th>Last error</th>
|
|
||||||
</tr>
|
|
||||||
{{range .}}<tr>
|
|
||||||
<td>
|
|
||||||
<form action="/start" method="get" id="form-{{.Name}}"></form>
|
|
||||||
<button{{if gt .CurrentRunningCount 0}} class="runningbg" {{else}}{{if .LastError}} class="errorbg" {{end}}{{end}} type="submit" form="form-{{.Name}}" name="jobName" value="{{.Name}}" {{if gt .CurrentRunningCount 0}} disabled{{end}}>{{.Name}}</button>
|
|
||||||
</td>
|
|
||||||
<td class="smaller">{{.JobConfig.Description}}</td>
|
|
||||||
<td class="nowrap" align="right">
|
|
||||||
<pre>{{.JobConfig.Cron}}</pre>
|
|
||||||
</td>
|
|
||||||
<td class="nowrap">{{if gt .CurrentRunningCount 1}}<span class="red">⯁ running {{.CurrentRunningCount}} jobs</span>{{else}}{{if .CurrentRunningCount}}<span class="green">⯈ running</span>{{else}}⯀ inactive{{end}}{{end}}</td>
|
|
||||||
<td>{{.LastStartTime}}</td>
|
|
||||||
<td>{{.LastEndTime}}</td>
|
|
||||||
<td align="right">{{.LastExecutionDuration}}</td>
|
|
||||||
<td>{{.NextLaunch}}</td>
|
|
||||||
<td class="smaller red">{{.LastError}}</td>
|
|
||||||
</tr>{{end}}
|
|
||||||
</table>
|
|
||||||
</main>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
9
job.go
9
job.go
@ -30,6 +30,7 @@ type Job struct {
|
|||||||
JobConfig JobConfig
|
JobConfig JobConfig
|
||||||
|
|
||||||
// Fields for stats
|
// Fields for stats
|
||||||
|
Status Status
|
||||||
CurrentRunningCount int
|
CurrentRunningCount int
|
||||||
LastStartTime string
|
LastStartTime string
|
||||||
LastEndTime string
|
LastEndTime string
|
||||||
@ -50,6 +51,7 @@ func readJob(filePath string) (*Job, error) {
|
|||||||
|
|
||||||
job := &Job{
|
job := &Job{
|
||||||
Name: strings.TrimSuffix(filepath.Base(filePath), filepath.Ext(filePath)),
|
Name: strings.TrimSuffix(filepath.Base(filePath), filepath.Ext(filePath)),
|
||||||
|
Status: Inactive,
|
||||||
JobConfig: jobConfig}
|
JobConfig: jobConfig}
|
||||||
|
|
||||||
return job, nil
|
return job, nil
|
||||||
@ -100,6 +102,7 @@ func (j *Job) Run() {
|
|||||||
|
|
||||||
globalMutex.Lock()
|
globalMutex.Lock()
|
||||||
j.CurrentRunningCount++
|
j.CurrentRunningCount++
|
||||||
|
j.Status = Running
|
||||||
j.LastStartTime = startTime.Format(config.TimeFormat)
|
j.LastStartTime = startTime.Format(config.TimeFormat)
|
||||||
globalMutex.Unlock()
|
globalMutex.Unlock()
|
||||||
|
|
||||||
@ -111,12 +114,14 @@ func (j *Job) Run() {
|
|||||||
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
j.Status = Error
|
||||||
log.Error(err.Error())
|
log.Error(err.Error())
|
||||||
|
|
||||||
globalMutex.Lock()
|
globalMutex.Lock()
|
||||||
j.LastError = err.Error()
|
j.LastError = err.Error()
|
||||||
globalMutex.Unlock()
|
globalMutex.Unlock()
|
||||||
} else {
|
} else {
|
||||||
|
j.Status = Inactive
|
||||||
globalMutex.Lock()
|
globalMutex.Lock()
|
||||||
j.LastError = ""
|
j.LastError = ""
|
||||||
globalMutex.Unlock()
|
globalMutex.Unlock()
|
||||||
@ -141,7 +146,9 @@ func (j *Job) Run() {
|
|||||||
|
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
log.Printf("Job failed, restarting in %d seconds.", j.JobConfig.RestartSec)
|
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)
|
log.Printf("Retry attempt №%d of %d failed, restarting in %d seconds.", i, j.JobConfig.NumberOfRestartAttemts, j.JobConfig.RestartSec)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("Retry attempt №%d of %d failed.", i, j.JobConfig.NumberOfRestartAttemts)
|
log.Printf("Retry attempt №%d of %d failed.", i, j.JobConfig.NumberOfRestartAttemts)
|
||||||
|
10
status.go
Normal file
10
status.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
type Status int
|
||||||
|
|
||||||
|
const (
|
||||||
|
Inactive Status = iota
|
||||||
|
Running
|
||||||
|
Error
|
||||||
|
Restarting
|
||||||
|
)
|
10
template.go
10
template.go
@ -1,20 +1,20 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
"embed"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed index.htm
|
//go:embed webui/*
|
||||||
var indexTemplateStr string
|
var siteFS embed.FS
|
||||||
|
|
||||||
var indexTemplate *template.Template
|
var templates *template.Template
|
||||||
|
|
||||||
func initTemplate() {
|
func initTemplate() {
|
||||||
var err error
|
var err error
|
||||||
indexTemplate, err = template.New("index").Parse(indexTemplateStr) // TODO: optimize
|
templates, err = template.ParseFS(siteFS, "webui/*.htm")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln("init template error:", err)
|
log.Fatalln("init template error:", err)
|
||||||
}
|
}
|
||||||
|
78
webui/details.htm
Normal file
78
webui/details.htm
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||||
|
<title>gron</title>
|
||||||
|
<link rel="stylesheet" href="/style.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<a class="dropdown dropbtn" href="/" class="dropbtn">← Back</a>
|
||||||
|
<h1>{{.Name}}</h1>
|
||||||
|
<table class="stats">
|
||||||
|
<tr>
|
||||||
|
<td>Description</td>
|
||||||
|
<td>{{.JobConfig.Description}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Cron</td>
|
||||||
|
<td><pre>{{.JobConfig.Cron}}</pre></td>
|
||||||
|
</td>
|
||||||
|
<tr>
|
||||||
|
<td>Command</td>
|
||||||
|
<td><pre>{{.JobConfig.Command}}</pre></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Status</td>
|
||||||
|
<td>{{if eq .Status 0}}⯀ inactive{{end}}{{if eq .Status 1}}<span class="green">⯈ running</span>{{end}}{{if eq .Status 2}}<span class="red">⯁ error</span>{{end}}{{if eq .Status 3}}<span class="orange">⟳ restarting</span>{{end}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
<h2>Stats</h2>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Start time</td>
|
||||||
|
<td>{{.LastStartTime}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Finish time</td>
|
||||||
|
<td>{{.LastEndTime}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Duration</td>
|
||||||
|
<td>{{.LastExecutionDuration}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Last error</td>
|
||||||
|
<td><pre class="red">{{.LastError}}</pre></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Next launch</td>
|
||||||
|
<td>{{.NextLaunch}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
<h2>On error action</h2>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Restart rule</td>
|
||||||
|
<td>{{if eq .JobConfig.RestartRule 0}}no restart{{end}}{{if eq .JobConfig.RestartRule 1}}restart on error{{end}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Number of restart attempts</td>
|
||||||
|
<td>{{.JobConfig.NumberOfRestartAttemts}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Restart delay</td>
|
||||||
|
<td>{{if eq .JobConfig.RestartSec 0}}none{{else}}{{.JobConfig.RestartSec}} sec{{end}}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
57
webui/index.htm
Normal file
57
webui/index.htm
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||||
|
<title>gron</title>
|
||||||
|
<link rel="stylesheet" href="/style.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<div class="dropdown">
|
||||||
|
<button class="dropbtn">☰ Menu</button>
|
||||||
|
<div class="dropdown-content">
|
||||||
|
<a href="/reloadJobs">⟳ Reload jobs</a>
|
||||||
|
<a>
|
||||||
|
<hr>
|
||||||
|
</a>
|
||||||
|
<a href="/shutdown">⏻ Shutdown</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h1>Job list</h1>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Description</th>
|
||||||
|
<th>Cron</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Start time</th>
|
||||||
|
<th>Finish time</th>
|
||||||
|
<th>Duration</th>
|
||||||
|
<th>Next launch</th>
|
||||||
|
<th>Details</th>
|
||||||
|
</tr>
|
||||||
|
{{range .}}
|
||||||
|
<tr>
|
||||||
|
<td class="no-padding">
|
||||||
|
<form action="/start" method="get" id="form-{{.Name}}"></form>
|
||||||
|
<button{{if gt .CurrentRunningCount 0}} class="runningbg" {{else}}{{if .LastError}} class="errorbg" {{end}}{{end}} type="submit" form="form-{{.Name}}" name="jobName" value="{{.Name}}" {{if gt .CurrentRunningCount 0}} disabled{{end}}>{{.Name}}</button>
|
||||||
|
</td>
|
||||||
|
<td class="smaller">{{.JobConfig.Description}}</td>
|
||||||
|
<td class="nowrap" align="right">
|
||||||
|
<pre>{{.JobConfig.Cron}}</pre>
|
||||||
|
</td>
|
||||||
|
<td class="nowrap">{{if eq .Status 0}}⯀ inactive{{end}}{{if eq .Status 1}}<span class="green">⯈ running</span>{{end}}{{if eq .Status 2}}<span class="red">⯁ error</span>{{end}}{{if eq .Status 3}}<span class="orange">⟳ restarting</span>{{end}}</td>
|
||||||
|
<td>{{.LastStartTime}}</td>
|
||||||
|
<td>{{.LastEndTime}}</td>
|
||||||
|
<td align="right">{{.LastExecutionDuration}}</td>
|
||||||
|
<td>{{.NextLaunch}}</td>
|
||||||
|
<td class="centered"><a href="/details?jobName={{.Name}}">open</a></td>
|
||||||
|
</tr>{{end}}
|
||||||
|
</table>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
203
webui/style.css
Normal file
203
webui/style.css
Normal file
@ -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;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user