Skip to content
This repository has been archived by the owner on Sep 4, 2024. It is now read-only.

Commit

Permalink
Merge pull request #17 from skroutz/web-view
Browse files Browse the repository at this point in the history
Add a Web view
  • Loading branch information
apostergiou authored Apr 19, 2018
2 parents 5fe3c4d + a2ac779 commit 5595bf9
Show file tree
Hide file tree
Showing 18 changed files with 862 additions and 11 deletions.
6 changes: 3 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
*.prof
*.pyc

config.json
fabfile.py

/mistry
/mistryd

cmd/mistryd/statik
config.json
fabfile.py
vendor/
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ services:
- docker
install:
- make deps
- go get github.com/rakyll/statik
script:
- make
addons:
Expand Down
7 changes: 5 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ install: fmt test

build: mistryd mistry

mistryd:
mistryd: generate
$(BUILDCMD) -o $(SERVER) cmd/mistryd/*.go

mistry:
$(BUILDCMD) -o $(CLIENT) cmd/mistry/*.go

test: mistry
test: generate mistry
$(TESTCMD) --filesystem plain

testall: test
Expand All @@ -34,3 +34,6 @@ fmt:

clean:
go clean ./...

generate:
go generate ./...
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Features include:
- running arbitrary commands inside isolated environments (provided as Docker images)
- build caching & incremental building (see [*"Build cache"*](https://github.com/skroutz/mistry/wiki/Build-cache))
- a simple JSON API for interacting with the server (scheduling jobs etc.)
- ([wip](https://github.com/skroutz/mistry/pull/17)) a web view for inspecting the progress and result of builds
- a web view for inspecting the progress of builds (see [*"Web view"*](#web-view))
- efficient use of disk space due to copy-on-write semantics (using [Btrfs snapshotting](https://en.wikipedia.org/wiki/Btrfs#Subvolumes_and_snapshots))

For more information visit the [wiki](https://github.com/skroutz/mistry/wiki).
Expand Down Expand Up @@ -108,6 +108,19 @@ $ curl -H 'Content-Type: application/json' -d '{"project": "foo"}' localhost:846



### Web view

*mistry* comes with a web view where progress and logs of each job can be
inspected.

Browse to http://0.0.0.0:8462 (or whatever address the server listens to).









Configuration
Expand Down
11 changes: 6 additions & 5 deletions cmd/mistryd/Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions cmd/mistryd/Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,7 @@
[[constraint]]
name = "github.com/urfave/cli"
version = "1.20.0"

[[constraint]]
name = "github.com/rakyll/statik"
version = "0.1.1"
68 changes: 68 additions & 0 deletions cmd/mistryd/job.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import (
"encoding/json"
"errors"
"fmt"
"html/template"
"io"
"log"
"os"
"path/filepath"
"sort"
"time"

dockertypes "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
Expand Down Expand Up @@ -49,6 +51,13 @@ type Job struct {
Image string
ImageTar []byte
Container string

StartedAt time.Time

// webview-related
Output string
Log template.HTML
State string
}

// NewJob returns a new Job for the given project. project and cfg cannot be
Expand Down Expand Up @@ -207,3 +216,62 @@ func (j *Job) String() string {
"{project=%s params=%s group=%s id=%s}",
j.Project, j.Params, j.Group, j.ID[:7])
}

func (j *Job) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
ID string `json:"id"`
Project string `json:"project"`
StartedAt string `json:"startedAt"`
Output string `json:"output"`
Log template.HTML `json:"log"`
State string `json:"state"`
}{
ID: j.ID,
Project: j.Project,
StartedAt: j.StartedAt.Format(DateFmt),
Output: j.Output,
Log: template.HTML(j.Log),
State: j.State,
})
}

func (j *Job) UnmarshalJSON(data []byte) error {
jData := &struct {
ID string `json:"id"`
Project string `json:"project"`
StartedAt string `json:"startedAt"`
Output string `json:"output"`
Log string `json:"log"`
State string `json:"state"`
}{}
err := json.Unmarshal(data, &jData)
if err != nil {
return err
}
j.ID = jData.ID
j.Project = jData.Project
j.StartedAt, err = time.Parse(DateFmt, jData.StartedAt)
if err != nil {
return err
}
j.Output = jData.Output
j.Log = template.HTML(jData.Log)
j.State = jData.State

return nil
}

// GetState determines the job's current state by using it's path in the filesystem.
func GetState(path, project, id string) (string, error) {
pPath := filepath.Join(path, project, "pending", id)
rPath := filepath.Join(path, project, "ready", id)
_, err := os.Stat(pPath)
if err == nil {
return "pending", nil
}
_, err = os.Stat(rPath)
if err == nil {
return "ready", nil
}
return "", fmt.Errorf("job with id=%s not found error", id)
}
2 changes: 2 additions & 0 deletions cmd/mistryd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ const (
// ImgCntPrefix is the common prefix added to the names of all
// Docker images/containers created by mistry.
ImgCntPrefix = "mistry-"

DateFmt = "Mon, 02 Jan 2006 15:04:05"
)

func main() {
Expand Down
1 change: 1 addition & 0 deletions cmd/mistryd/public/css/foundation.min.css

Large diffs are not rendered by default.

34 changes: 34 additions & 0 deletions cmd/mistryd/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>The mistry jobs</title>
<link rel="stylesheet" href="css/foundation.min.css">
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<script type="text/babel" src="js/index.js"></script>
</head>

<body>
<div class="top-bar">
<div class="top-bar-left">
<h1><a href="/">mistry</a></h1>
</div>
</div>

<div class="grid-container">
<div class="grid-x grid-padding-x">

<div class="large-12 cell">
<h1>Jobs</h1>
</div>

<div class="large-12 cell" id="js-jobs">
</div>
</div>
</div>
</body>
</html>
62 changes: 62 additions & 0 deletions cmd/mistryd/public/js/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
const JobsRoot = document.getElementById('js-jobs')

class Jobs extends React.Component {
constructor(props) {
super(props)
this.every = props.every
this.state = {}
};

fetchJobs() {
fetch("/index").
then(response => response.json()).
then(data => this.setState({ jobs: data }));
};

componentDidMount() {
this.fetchJobs();
this.interval = setInterval(() => this.fetchJobs(), this.every);
};

componentWillUnmount() {
clearInterval(this.interval);
};

render() {
if (this.state.jobs == undefined) {
return (
<div class="jumbotron">
<h3>No jobs...</h3>
</div>
);
}

let jobs = this.state.jobs;
return (
<table class="hover unstriped">
<thead>
<tr>
<th>ID</th>
<th>Project</th>
<th>Started At</th>
<th>State</th>
</tr>
</thead>
<tbody>
{jobs.map(function(j, idx){
return (
<tr key={idx}>
<td><a href={`/job/${j.project}/${j.id}`} > {j.id} </a></td>
<td>{j.project}</td>
<td>{j.startedAt}</td>
<td>{j.state}</td>
</tr>
)
})}
</tbody>
</table>
);
}
}

ReactDOM.render(<Jobs every={3000} />, JobsRoot)
94 changes: 94 additions & 0 deletions cmd/mistryd/public/templates/show.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Job Logs</title>
<link rel="stylesheet" href="../../css/foundation.min.css">
</head>

<body>
<div class="top-bar">
<div class="top-bar-left">
<h1><a href="/">mistry</a></h1>
</div>
</div>

<div class="grid-container">
<div class="grid">
<div class="cell">
<h4>Job: {{.ID}}</h4>
</div>
<div class="cell">
<div class="card">
<div class="card-section">
<div class="card-divider">
<h4> Details </h4>
</div>
<p id="js-job-info"></p>
</div>
</div>
</div>
<div class="cell">
<div class="card">
<div class="card-section">
<div class="card-divider">
<h4> Logs </h4>
</div>
<p id="js-job-log">{{.Log}}</p>
</div>
</div>
</div>
</div>

<script type="text/javascript">
const jobInfo = document.getElementById('js-job-info');
const state = {{.State}}

jobInfo.innerHTML += "Project: ".big() + {{.Project}} + "<br>";
jobInfo.innerHTML += "State: ".big() + {{.State}} + "<br>";
jobInfo.innerHTML += "Started at: ".big() + {{.StartedAt}} + "<br>";

if (state == "ready") {
const output = {{.Output}}
const jobJSON = JSON.parse(output);
jobInfo.innerHTML += "Path: ".big() + jobJSON["Path"] + "<br>";
jobInfo.innerHTML += "Params: ".big() + JSON.stringify(jobJSON.Params) + "<br>";
jobInfo.innerHTML += "Cached: ".big() + jobJSON["Cached"] + "<br>";
jobInfo.innerHTML += "Coalesced: ".big() + jobJSON["Coalesced"] + "<br>";
jobInfo.innerHTML += "Error: ".big() + jobJSON["Err"] + "<br>";
jobInfo.innerHTML += "ExitCode: ".big() + jobJSON["ExitCode"] + "<br>";
jobInfo.innerHTML += "Transport method: ".big() + jobJSON["TransportMethod"] + "<br>";
}

if (state == "pending") {
setInterval(checkState, 3000);

function checkState() {
let jHeaders = new Headers();
jHeaders.append('Content-Type', 'application/json');
const jobRequest = new Request('/job/{{.Project}}/{{.ID}}', {headers: jHeaders});
fetch(jobRequest)
.then(function(response) { return response.json(); })
.then(function(data) {
if (data["State"] == "ready"){
location.reload();
}
})
}

const source = new EventSource('/log/{{.Project}}/{{.ID}}');
const jobLog = document.getElementById('js-job-log')
source.onmessage = function(e) {
jobLog.innerHTML += e.data + "</br>"
};

source.onerror = function(e) {
document.body.innerHTML += "Web tail failed.";
source.close();
};
}
</script>
</body>
</html>
Loading

0 comments on commit 5595bf9

Please sign in to comment.