add HTML output to arthur
This commit is contained in:
parent
8078330a2e
commit
900ac27d63
|
@ -1,21 +1,24 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
_ "embed"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html/template"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
serverArthurPkg "git.csclub.uwaterloo.ca/public/merlin/arthur"
|
serverArthurPkg "git.csclub.uwaterloo.ca/public/merlin/arthur"
|
||||||
)
|
)
|
||||||
|
|
||||||
// var DEFAULT_SOCKET_PATH = "/home/mirror/merlin/merlin.sock"
|
var DEFAULT_SOCKET_PATH = "/mirror/merlin/run/merlin.sock"
|
||||||
var DEFAULT_SOCKET_PATH = "/mirror/merlin/run/merlin-go.sock"
|
|
||||||
|
|
||||||
var HELP_MESSAGE = `USAGE:
|
var HELP_MESSAGE = `USAGE:
|
||||||
arthur [-h|--help] [-json] [-sock <socket_path>] COMMAND
|
arthur [-h|--help] [-json] [-sock <socket_path>] COMMAND
|
||||||
|
@ -27,6 +30,9 @@ COMMANDS:
|
||||||
FLAGS:
|
FLAGS:
|
||||||
`
|
`
|
||||||
|
|
||||||
|
//go:embed layout.html
|
||||||
|
var layoutHtmlTemplate string
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.SetPrefix("[ERROR]: ")
|
log.SetPrefix("[ERROR]: ")
|
||||||
log.SetFlags(0)
|
log.SetFlags(0)
|
||||||
|
@ -43,13 +49,15 @@ func main() {
|
||||||
|
|
||||||
var shouldOutputJson bool
|
var shouldOutputJson bool
|
||||||
flag.BoolVar(&shouldOutputJson, "json", false, "JSON output")
|
flag.BoolVar(&shouldOutputJson, "json", false, "JSON output")
|
||||||
|
var shouldOutputHtml bool
|
||||||
|
flag.BoolVar(&shouldOutputHtml, "html", false, "HTML output")
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if flag.NArg() < 1 {
|
if flag.NArg() < 1 {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
command := os.Args[1]
|
command := flag.Args()[0]
|
||||||
|
|
||||||
conn, err := net.Dial("unix", *sockPath)
|
conn, err := net.Dial("unix", *sockPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -67,22 +75,43 @@ func main() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
// TODO: merlin response should always be JSON
|
||||||
|
if strings.HasPrefix(command, "sync") {
|
||||||
|
fmt.Println(string(response))
|
||||||
|
return
|
||||||
|
}
|
||||||
statusInfo := serverArthurPkg.StatusInfo{}
|
statusInfo := serverArthurPkg.StatusInfo{}
|
||||||
err = json.Unmarshal(response, &statusInfo)
|
err = json.Unmarshal(response, &statusInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if command == "status" && shouldOutputJson {
|
if shouldOutputJson {
|
||||||
jsonOutput, jsonErr := json.MarshalIndent(&statusInfo, "", " ")
|
jsonOutput, jsonErr := json.MarshalIndent(&statusInfo, "", " ")
|
||||||
if jsonErr == nil {
|
if jsonErr == nil {
|
||||||
fmt.Println(string(jsonOutput))
|
fmt.Println(string(jsonOutput))
|
||||||
} else {
|
} else {
|
||||||
log.Fatal(jsonErr)
|
log.Fatal(jsonErr)
|
||||||
}
|
}
|
||||||
return
|
} else if shouldOutputHtml {
|
||||||
|
funcs := template.FuncMap{
|
||||||
|
"unixTimeToStr": unixTimeToStr,
|
||||||
|
"minus": func(a, b int64) int64 { return a - b },
|
||||||
}
|
}
|
||||||
|
tmpl := template.Must(template.New("layout").
|
||||||
|
Funcs(funcs).
|
||||||
|
Parse(layoutHtmlTemplate))
|
||||||
|
var buf bytes.Buffer
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"Repos": statusInfo.Repos,
|
||||||
|
"Now": time.Now().Unix(),
|
||||||
|
}
|
||||||
|
err = tmpl.Execute(&buf, data)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Print(buf.String())
|
||||||
|
} else {
|
||||||
writer := tabwriter.NewWriter(os.Stdout, 5, 5, 5, ' ', 0)
|
writer := tabwriter.NewWriter(os.Stdout, 5, 5, 5, ' ', 0)
|
||||||
// print out the state of each repo in the config (last and next sync time + if it is currently running)
|
// print out the state of each repo in the config (last and next sync time + if it is currently running)
|
||||||
fmt.Fprintf(writer, "Repository\tLast Synced\tNext Expected Sync\tLast Exit\tRunning\n")
|
fmt.Fprintf(writer, "Repository\tLast Synced\tNext Expected Sync\tLast Exit\tRunning\n")
|
||||||
|
@ -97,6 +126,7 @@ func main() {
|
||||||
}
|
}
|
||||||
writer.Flush()
|
writer.Flush()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var location *time.Location
|
var location *time.Location
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>CSC Mirror Status</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
|
||||||
|
"Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif,
|
||||||
|
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||||
|
}
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.main-title {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.generated-at {
|
||||||
|
margin: 2rem 0;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.repos {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(min(400px, 95%), 1fr));
|
||||||
|
grid-auto-rows: auto;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
.repo {
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
padding: 1rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
.repo-name {
|
||||||
|
width: max-content;
|
||||||
|
margin: 0 0 0.5rem 0;
|
||||||
|
font-size: 1.125rem;
|
||||||
|
line-height: 1.75rem;
|
||||||
|
}
|
||||||
|
.repo-data-item {
|
||||||
|
padding-left: 0.5rem;
|
||||||
|
text-indent: -0.5rem;
|
||||||
|
}
|
||||||
|
.succeeded {
|
||||||
|
background-color: rgb(187, 247, 208);
|
||||||
|
}
|
||||||
|
.out-of-date {
|
||||||
|
background-color: rgb(252, 165, 165);
|
||||||
|
}
|
||||||
|
.failed {
|
||||||
|
background-color: rgb(254, 202, 202);
|
||||||
|
}
|
||||||
|
.unknown {
|
||||||
|
background-color: rgb(229, 229, 229);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1 class="main-title">CSC Mirror Status</h1>
|
||||||
|
<div class="generated-at">
|
||||||
|
<div>Generated at: </div>
|
||||||
|
<div>{{unixTimeToStr .Now}}</div>
|
||||||
|
</div>
|
||||||
|
<ul class="repos">
|
||||||
|
{{range .Repos}}
|
||||||
|
{{$outOfDate := (gt (minus $.Now .LastSuccessfulAttemptStartTime) 86400)}}
|
||||||
|
{{$succeeded := (or (eq .LastAttemptStartTime .LastSuccessfulAttemptStartTime) (and .IsRunning (eq .LastAttemptExit "completed")))}}
|
||||||
|
{{$failed := (eq .LastAttemptExit "failed")}}
|
||||||
|
<li class="repo {{if $outOfDate}} out-of-date {{else if $succeeded}} succeeded {{else if $failed}} failed {{else}} unknown {{end}}">
|
||||||
|
<a href="./{{.Name}}">
|
||||||
|
<h4 class="repo-name">{{.Name}}</h4>
|
||||||
|
</a>
|
||||||
|
<div class="repo-data-item">
|
||||||
|
{{if .IsRunning}} Is currently syncing {{else}} Is not syncing {{end}}
|
||||||
|
</div>
|
||||||
|
<div class="repo-data-item">
|
||||||
|
Last sync time: {{unixTimeToStr .LastAttemptStartTime}}
|
||||||
|
</div>
|
||||||
|
<div class="repo-data-item">
|
||||||
|
Last sync result: {{.LastAttemptExit}}
|
||||||
|
</div>
|
||||||
|
<div class="repo-data-item">
|
||||||
|
Last successful sync time: {{unixTimeToStr .LastSuccessfulAttemptStartTime}}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{{end}}
|
||||||
|
</ul>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue