mirror of
https://github.com/nxshock/gron.git
synced 2024-11-27 03:41:00 +05:00
Add JS for online data update
This commit is contained in:
parent
733d5f28f1
commit
d9d9f0dcf0
@ -1,8 +1,6 @@
|
||||
package main
|
||||
|
||||
const (
|
||||
programName = "gron"
|
||||
|
||||
defaultConfigFileName = "gron.conf"
|
||||
|
||||
defaultOnSuccessMessageFmt = "Job {{.JobName}} finished."
|
||||
|
9
go.mod
9
go.mod
@ -3,14 +3,15 @@ module github.com/nxshock/gron
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.2.0
|
||||
github.com/BurntSushi/toml v1.2.1
|
||||
github.com/antonfisher/nested-logrus-formatter v1.3.1
|
||||
github.com/creasty/defaults v1.6.0
|
||||
github.com/denisenkom/go-mssqldb v0.12.3
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/jackc/pgx v3.6.2+incompatible
|
||||
github.com/nxshock/logwriter v0.0.0-20220514172136-b1385d4106de
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/sijms/go-ora/v2 v2.5.3
|
||||
github.com/sijms/go-ora/v2 v2.5.6
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/stretchr/testify v1.8.0
|
||||
)
|
||||
@ -28,8 +29,8 @@ require (
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
golang.org/x/crypto v0.1.0 // indirect
|
||||
golang.org/x/sys v0.1.0 // indirect
|
||||
golang.org/x/crypto v0.3.0 // indirect
|
||||
golang.org/x/sys v0.2.0 // indirect
|
||||
golang.org/x/text v0.4.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
18
go.sum
18
go.sum
@ -1,8 +1,8 @@
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8=
|
||||
github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0=
|
||||
github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
|
||||
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/antonfisher/nested-logrus-formatter v1.3.1 h1:NFJIr+pzwv5QLHTPyKz9UMEoHck02Q9L0FP13b/xSbQ=
|
||||
github.com/antonfisher/nested-logrus-formatter v1.3.1/go.mod h1:6WTfyWFkBc9+zyBaKIqRrg/KwMqBbodBjgbHjDz7zjA=
|
||||
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
|
||||
@ -23,6 +23,8 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0kt
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
|
||||
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 h1:vr3AYkKovP8uR8AvSGGUK1IDqRa5lAAvEkZG1LKaCRc=
|
||||
github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ=
|
||||
github.com/jackc/pgx v3.6.2+incompatible h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yIyTQf3/o=
|
||||
@ -53,8 +55,8 @@ github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBO
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
||||
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/sijms/go-ora/v2 v2.5.3 h1:klGKmhqRONVTtIzTdfYTvrW94kdJkdmZl93u2A3vchI=
|
||||
github.com/sijms/go-ora/v2 v2.5.3/go.mod h1:EHxlY6x7y9HAsdfumurRfTd+v8NrEOTR3Xl4FWlH6xk=
|
||||
github.com/sijms/go-ora/v2 v2.5.6 h1:V53uhbcVpPrGBICnwBJx780+lcqfbTWLCi376B7Dr5A=
|
||||
github.com/sijms/go-ora/v2 v2.5.6/go.mod h1:EHxlY6x7y9HAsdfumurRfTd+v8NrEOTR3Xl4FWlH6xk=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@ -66,8 +68,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
@ -78,8 +80,8 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
|
@ -8,11 +8,49 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type WsConnections struct {
|
||||
connections map[*websocket.Conn]struct{}
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func (wc *WsConnections) Add(c *websocket.Conn) {
|
||||
wc.mutex.Lock()
|
||||
defer wc.mutex.Unlock()
|
||||
|
||||
wc.connections[c] = struct{}{}
|
||||
}
|
||||
|
||||
func (wc *WsConnections) Delete(c *websocket.Conn) {
|
||||
wc.mutex.Lock()
|
||||
defer wc.mutex.Unlock()
|
||||
|
||||
delete(wc.connections, c)
|
||||
}
|
||||
|
||||
func (wc *WsConnections) Send(message interface{}) {
|
||||
for conn := range wc.connections {
|
||||
go func(conn *websocket.Conn) { _ = conn.WriteJSON(message) }(conn)
|
||||
}
|
||||
}
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
var wsConnections = &WsConnections{
|
||||
connections: make(map[*websocket.Conn]struct{})}
|
||||
|
||||
func httpServer(listenAddress string) {
|
||||
if listenAddress == "none" {
|
||||
return
|
||||
@ -23,6 +61,7 @@ func httpServer(listenAddress string) {
|
||||
http.HandleFunc("/shutdown", handleShutdown)
|
||||
http.HandleFunc("/start", handleForceStart)
|
||||
http.HandleFunc("/details", handleDetails)
|
||||
http.HandleFunc("/ws", handleWebSocket)
|
||||
log.WithField("job", "http_server").Fatal(http.ListenAndServe(listenAddress, nil))
|
||||
}
|
||||
|
||||
@ -39,10 +78,9 @@ func handler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
globalMutex.RLock()
|
||||
buf := new(bytes.Buffer)
|
||||
jobEntries := kernel.c.Entries()
|
||||
|
||||
jobs := make(map[string][]*Job)
|
||||
for _, jobEntry := range jobEntries {
|
||||
for _, jobEntry := range kernel.c.Entries() {
|
||||
job := jobEntry.Job.(*Job)
|
||||
job.NextLaunch = jobEntry.Next.Format(config.TimeFormat)
|
||||
jobs[job.JobConfig.Category] = append(jobs[job.JobConfig.Category], job)
|
||||
@ -73,6 +111,41 @@ func handler(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = buf.WriteTo(w)
|
||||
}
|
||||
|
||||
func handleWebSocket(w http.ResponseWriter, r *http.Request) {
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
wsConnections.Add(conn)
|
||||
defer wsConnections.Delete(conn)
|
||||
|
||||
var startMessage struct {
|
||||
JobName string
|
||||
}
|
||||
|
||||
for {
|
||||
err := conn.ReadJSON(&startMessage)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
break
|
||||
}
|
||||
|
||||
for _, jobEntry := range kernel.c.Entries() {
|
||||
job := jobEntry.Job.(*Job)
|
||||
if job.Name == startMessage.JobName {
|
||||
host, _, err := net.SplitHostPort(conn.RemoteAddr().String())
|
||||
if err != nil {
|
||||
host = r.RemoteAddr
|
||||
}
|
||||
log.WithField("job", "http_server").Printf("Forced start %s from %s.", job.Name, host)
|
||||
go job.Run()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleForceStart(w http.ResponseWriter, r *http.Request) {
|
||||
jobName := r.FormValue("jobName")
|
||||
if jobName == "" {
|
||||
@ -80,9 +153,7 @@ func handleForceStart(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
jobEntries := kernel.c.Entries()
|
||||
|
||||
for _, jobEntry := range jobEntries {
|
||||
for _, jobEntry := range kernel.c.Entries() {
|
||||
job := jobEntry.Job.(*Job)
|
||||
if job.Name == jobName {
|
||||
host, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
|
23
job.go
23
job.go
@ -128,6 +128,16 @@ func (j *Job) openAndMergeLog() (logEntry *log.Entry, jobLogFile *os.File) {
|
||||
}
|
||||
|
||||
func (j *Job) Run() {
|
||||
// TODO: переписать неоптимальный цикл
|
||||
for _, jobEntry := range kernel.c.Entries() {
|
||||
if jobEntry.Job.(*Job) != j {
|
||||
continue
|
||||
}
|
||||
|
||||
j.NextLaunch = jobEntry.Next.Format(config.TimeFormat)
|
||||
break
|
||||
}
|
||||
|
||||
log, jobLogFile := j.openAndMergeLog()
|
||||
defer jobLogFile.Close()
|
||||
|
||||
@ -165,6 +175,8 @@ func (j *Job) runTry(log *log.Entry, jobLogFile *os.File) error {
|
||||
j.LastStartTime = startTime.Format(config.TimeFormat)
|
||||
globalMutex.Unlock()
|
||||
|
||||
wsConnections.Send(j)
|
||||
|
||||
var err error
|
||||
switch j.JobConfig.Type {
|
||||
case Cmd:
|
||||
@ -196,6 +208,8 @@ func (j *Job) runTry(log *log.Entry, jobLogFile *os.File) error {
|
||||
j.LastExecutionDuration = endTime.Sub(startTime).Truncate(time.Second).String()
|
||||
globalMutex.Unlock()
|
||||
|
||||
wsConnections.Send(j)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@ -318,12 +332,5 @@ func (j *Job) errorMessage(err error) string {
|
||||
}
|
||||
|
||||
func runSimpleCmd(command string, args ...string) error {
|
||||
log.Println(command)
|
||||
log.Println(args)
|
||||
err := exec.Command(command, args...).Run()
|
||||
if err != nil {
|
||||
log.Println(">>", err)
|
||||
}
|
||||
|
||||
return err
|
||||
return exec.Command(command, args...).Run()
|
||||
}
|
||||
|
@ -8,6 +8,6 @@ import (
|
||||
func format(fmt string, v interface{}) string {
|
||||
t := new(template.Template)
|
||||
b := new(strings.Builder)
|
||||
template.Must(t.Parse(fmt)).Execute(b, v) // TODO: обработать возможные ошибки
|
||||
_ = template.Must(t.Parse(fmt)).Execute(b, v) // TODO: обработать возможные ошибки
|
||||
return b.String()
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<title>gron</title>
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
<link rel="icon" href="data:,">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@ -35,10 +36,9 @@
|
||||
<th>Details</th>
|
||||
</tr>
|
||||
{{range (index $.Jobs .)}}
|
||||
<tr>
|
||||
<tr id="{{.Name}}">
|
||||
<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>
|
||||
<button{{if gt .CurrentRunningCount 0}} class="runningbg" {{else}}{{if .LastError}} class="errorbg" {{end}}{{end}} name="jobName" value="{{.Name}}" {{if gt .CurrentRunningCount 0}} disabled{{end}} onclick='startJob("{{.Name}}")'>{{.Name}}</button>
|
||||
</td>
|
||||
<td class="smaller">{{.JobConfig.Description}}</td>
|
||||
<td class="nowrap" align="right">
|
||||
@ -54,5 +54,54 @@
|
||||
</table>{{end}}
|
||||
</main>
|
||||
</body>
|
||||
<script>
|
||||
let socket = new WebSocket("ws://" + window.location.host + "/ws")
|
||||
|
||||
socket.onerror = function(error) {
|
||||
console.log("WebSocket error: " + JSON.stringify(error))
|
||||
socket.close()
|
||||
}
|
||||
|
||||
socket.onmessage = function(event) {
|
||||
message = JSON.parse(event.data);
|
||||
|
||||
html4 = "unknown"
|
||||
if (message.Status == 0) {
|
||||
html4 = "⯀ inactive"
|
||||
} else if (message.Status == 1) {
|
||||
html4 = '<span class="green">⯈ running</span>'
|
||||
} else if (message.Status == 2) {
|
||||
html4 = '<span class="red">⯁ error</span>'
|
||||
} else if (message.Status == 3) {
|
||||
html4 = '<span class="orange">⟳ restarting</span>'
|
||||
}
|
||||
|
||||
if (message.CurrentRunningCount > 0) {
|
||||
document.querySelector("#" + message.Name + " > td:nth-child(1) > button").className = "runningbg"
|
||||
} else if (message.LastError != "") {
|
||||
document.querySelector("#" + message.Name + " > td:nth-child(1) > button").className = "errorbg"
|
||||
} else {
|
||||
document.querySelector("#" + message.Name + " > td:nth-child(1) > button").removeAttribute("class")
|
||||
}
|
||||
|
||||
if (message.CurrentRunningCount > 0) {
|
||||
document.querySelector("#" + message.Name + " > td:nth-child(1) > button").setAttribute("disabled", "true")
|
||||
} else {
|
||||
document.querySelector("#" + message.Name + " > td:nth-child(1) > button").removeAttribute("disabled")
|
||||
}
|
||||
|
||||
document.querySelector("#" + message.Name + " > td:nth-child(4)").innerHTML = html4
|
||||
document.querySelector("#" + message.Name + " > td:nth-child(5)").innerHTML = message.LastStartTime
|
||||
document.querySelector("#" + message.Name + " > td:nth-child(6)").innerHTML = message.LastEndTime
|
||||
document.querySelector("#" + message.Name + " > td:nth-child(7)").innerHTML = message.LastExecutionDuration
|
||||
document.querySelector("#" + message.Name + " > td:nth-child(8)").innerHTML = message.NextLaunch
|
||||
}
|
||||
|
||||
function startJob(jobName) {
|
||||
socket.send(JSON.stringify({
|
||||
jobName
|
||||
}));
|
||||
}
|
||||
</script>
|
||||
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user