Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
70c9f60d8f | |||
a754264a1d | |||
7082d69bab | |||
e0bc90e954 | |||
5f26454adf | |||
d117c3e664 | |||
ae961e9dd1 | |||
7ca04a5594 | |||
a1ced419c0 |
15
cycle.go
15
cycle.go
@ -7,6 +7,7 @@ package main
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@ -107,12 +108,13 @@ func (p *Project) fetch() {
|
||||
|
||||
p.setVersion(strings.TrimSpace(string(version)))
|
||||
|
||||
lVer, err := p.ds.LastVersion("")
|
||||
// check if this specific version has attempted a build yet
|
||||
lVer, err := p.ds.LastVersion(stageBuild)
|
||||
if err != datastore.ErrNotFound && p.errHandled(err) {
|
||||
return
|
||||
}
|
||||
|
||||
if p.version == "" || p.version == lVer {
|
||||
if p.version == "" || p.version == lVer.Version {
|
||||
// no new build clean up temp dir
|
||||
p.errHandled(os.RemoveAll(tempDir))
|
||||
|
||||
@ -218,8 +220,15 @@ func (p *Project) release() {
|
||||
return
|
||||
}
|
||||
|
||||
p.setStage(stageReleased)
|
||||
|
||||
if p.errHandled(p.ds.AddLog(p.version, p.stage,
|
||||
fmt.Sprintf("Project %s Version %s built, tested, and released successfully.\n", p.id(), p.version))) {
|
||||
return
|
||||
}
|
||||
|
||||
//build successfull, remove working dir
|
||||
p.errHandled(os.RemoveAll(p.workingDir()))
|
||||
|
||||
vlog("Project: %s Version %s built, tested, and released successfully.\n", p.id(), p.version)
|
||||
vlog("Project %s Version %s built, tested, and released successfully.\n", p.id(), p.version)
|
||||
}
|
||||
|
@ -6,10 +6,8 @@
|
||||
package datastore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
@ -77,15 +75,6 @@ func (ds *Store) get(bucket string, key []byte, result interface{}) error {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
if value, ok := result.([]byte); ok {
|
||||
buff := bytes.NewBuffer(value)
|
||||
_, err := io.Copy(buff, bytes.NewReader(dsValue))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return json.Unmarshal(dsValue, result)
|
||||
})
|
||||
}
|
||||
|
@ -37,8 +37,8 @@ func (ds *Store) AddLog(version, stage, entry string) error {
|
||||
|
||||
// LastVersion returns the last version in the log for the given stage. If stage is blank,
|
||||
// then it returns the last of any stage
|
||||
func (ds *Store) LastVersion(stage string) (string, error) {
|
||||
version := ""
|
||||
func (ds *Store) LastVersion(stage string) (*Log, error) {
|
||||
last := &Log{}
|
||||
|
||||
err := ds.bolt.View(func(tx *bolt.Tx) error {
|
||||
c := tx.Bucket([]byte(bucketLog)).Cursor()
|
||||
@ -52,7 +52,7 @@ func (ds *Store) LastVersion(stage string) (string, error) {
|
||||
|
||||
if l.Version != "" {
|
||||
if stage == "" || l.Stage == stage {
|
||||
version = l.Version
|
||||
last = l
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@ -62,10 +62,10 @@ func (ds *Store) LastVersion(stage string) (string, error) {
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return version, nil
|
||||
return last, nil
|
||||
}
|
||||
|
||||
// Versions lists the versions in a given project, including the last stage that version got to
|
||||
@ -86,7 +86,6 @@ func (ds *Store) Versions() ([]*Log, error) {
|
||||
|
||||
// capture the newest entry for each version
|
||||
if l.Version != current {
|
||||
l.Log = "" // only care about date, ver and stage
|
||||
vers = append(vers, l)
|
||||
current = l.Version
|
||||
}
|
||||
|
@ -5,7 +5,9 @@
|
||||
package datastore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
@ -52,13 +54,27 @@ func (ds *Store) AddRelease(version, fileName string, fileData []byte) error {
|
||||
|
||||
// ReleaseFile returns a specific file from a release for the given file key
|
||||
func (ds *Store) ReleaseFile(fileKey TimeKey) ([]byte, error) {
|
||||
var fileData []byte
|
||||
err := ds.get(bucketFiles, fileKey.Bytes(), fileData)
|
||||
var fileData bytes.Buffer
|
||||
|
||||
err := ds.bolt.View(func(tx *bolt.Tx) error {
|
||||
dsValue := tx.Bucket([]byte(bucketFiles)).Get(fileKey.Bytes())
|
||||
|
||||
if dsValue == nil {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
_, err := io.Copy(&fileData, bytes.NewReader(dsValue))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return fileData, nil
|
||||
return fileData.Bytes(), nil
|
||||
}
|
||||
|
||||
// Release gets the release record for a specific version
|
||||
|
2
exec.go
2
exec.go
@ -27,7 +27,7 @@ func runCmd(cmd, dir string) ([]byte, error) {
|
||||
|
||||
result, err := ec.CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s:\n%s", err, result)
|
||||
return nil, fmt.Errorf("%s", result)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
27
project.go
27
project.go
@ -25,12 +25,13 @@ const (
|
||||
|
||||
//stages
|
||||
const (
|
||||
stageLoad = "loading"
|
||||
stageFetch = "fetching"
|
||||
stageBuild = "building"
|
||||
stageTest = "testing"
|
||||
stageRelease = "releasing"
|
||||
stageWait = "waiting"
|
||||
stageLoad = "loading"
|
||||
stageFetch = "fetching"
|
||||
stageBuild = "building"
|
||||
stageTest = "testing"
|
||||
stageRelease = "releasing"
|
||||
stageReleased = "released"
|
||||
stageWait = "waiting"
|
||||
)
|
||||
|
||||
const projectFilePoll = 30 * time.Second
|
||||
@ -185,11 +186,11 @@ func (p *Project) setStage(stage string) {
|
||||
}
|
||||
|
||||
type webProject struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
ReleaseVersion string `json:"releaseVersion"` //last successfully released version
|
||||
LastVersion string `json:"lastVersion"` //last version success or otherwise
|
||||
Stage string `json:"stage"` // current stage
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
ReleaseVersion string `json:"releaseVersion"` //last successfully released version
|
||||
Stage string `json:"stage"` // current stage
|
||||
LastLog *datastore.Log `json:"lastLog"`
|
||||
}
|
||||
|
||||
func (p *Project) webData() (*webProject, error) {
|
||||
@ -209,9 +210,9 @@ func (p *Project) webData() (*webProject, error) {
|
||||
d := &webProject{
|
||||
Name: p.Name,
|
||||
ID: p.id(),
|
||||
LastVersion: last,
|
||||
ReleaseVersion: release,
|
||||
ReleaseVersion: release.Version,
|
||||
Stage: p.stage,
|
||||
LastLog: last,
|
||||
}
|
||||
|
||||
return d, nil
|
||||
|
17
server.go
17
server.go
@ -103,6 +103,14 @@ func routes() {
|
||||
get: rootGet,
|
||||
})
|
||||
|
||||
webRoot.Handle("/js/", &methodHandler{
|
||||
get: assetGet,
|
||||
})
|
||||
|
||||
webRoot.Handle("/css/", &methodHandler{
|
||||
get: assetGet,
|
||||
})
|
||||
|
||||
webRoot.Handle("/log/", &methodHandler{
|
||||
get: logGet,
|
||||
})
|
||||
@ -118,12 +126,11 @@ func routes() {
|
||||
}
|
||||
|
||||
func rootGet(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/" {
|
||||
//send index.html
|
||||
serveAsset(w, r, "web/index.html")
|
||||
return
|
||||
}
|
||||
//send index.html
|
||||
serveAsset(w, r, "web/index.html")
|
||||
}
|
||||
|
||||
func assetGet(w http.ResponseWriter, r *http.Request) {
|
||||
serveAsset(w, r, path.Join("web", r.URL.Path))
|
||||
}
|
||||
|
||||
|
243
web/index.html
243
web/index.html
@ -10,11 +10,254 @@
|
||||
<link rel="stylesheet" href="/css/pure-min.css">
|
||||
|
||||
<style>
|
||||
.container {
|
||||
padding-right: 15px;
|
||||
padding-left: 15px;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
padding-right: 40px;
|
||||
padding-left: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.center-block {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/*tables*/
|
||||
|
||||
.table-responsive {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.table-responsive table {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* error */
|
||||
.error {
|
||||
display: inline-block;
|
||||
background-color: red;
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
padding: .5em 1em;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
/* breadcrumbs */
|
||||
|
||||
#breadcrumbs {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.breadcrumb-separator {
|
||||
color: #ccc;
|
||||
font-weight: bold;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
|
||||
.pull-left {
|
||||
float: left;
|
||||
}
|
||||
.pull-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.timestamp {
|
||||
font-size: .75em;
|
||||
color: #777;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.log {
|
||||
margin-left: 15px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.log > pre {
|
||||
margin-left: 15px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script id="tMain" type="text/ractive">
|
||||
<div class="container pure-g">
|
||||
<div class="pure-u-1">
|
||||
<h3 class="text-center">Iron Smith</h3>
|
||||
{{#if error}}
|
||||
<div class="text-center">
|
||||
<span class="error">{{error}}</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div id="breadcrumbs" class="pure-menu pure-menu-horizontal text-center">
|
||||
<ul class="pure-menu-list">
|
||||
<li class="pure-menu-item">
|
||||
<a href="/" class="pure-menu-link">Project List</a>
|
||||
</li>
|
||||
{{#if project}}
|
||||
<li class="pure-menu-item">
|
||||
<span class="breadcrumb-separator">/</span>
|
||||
</li>
|
||||
{{#if !version && !currentStage}}
|
||||
<li class="pure-menu-item pure-menu-has-children" decorator="menu">
|
||||
<a href="#" id="projectMenu" class="pure-menu-link">{{project.name}}</a>
|
||||
<ul class="pure-menu-children">
|
||||
<li class="pure-menu-item">
|
||||
<a href="#" class="pure-menu-link" on-click="triggerBuild">Trigger Build</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
{{else}}
|
||||
<li class="pure-menu-item">
|
||||
<a href="/project/{{project.id}}" class="pure-menu-link">{{project.name}}</a>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#if project && version}}
|
||||
<li class="pure-menu-item">
|
||||
<span class="breadcrumb-separator">/</span>
|
||||
</li>
|
||||
<li class="pure-menu-item">
|
||||
<a href="/project/{{project.id}}/{{version}}" class="pure-menu-link">{{version}}</a>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if project && version && currentStage}}
|
||||
<li class="pure-menu-item">
|
||||
<span class="breadcrumb-separator">/</span>
|
||||
</li>
|
||||
<li class="pure-menu-item">
|
||||
<a href="/project/{{project.id}}/{{version}}/{{currentStage}}" class="pure-menu-link">{{currentStage}}</a>
|
||||
</li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
</div>
|
||||
{{#if !project}}
|
||||
{{>projects}}
|
||||
{{elseif !version}}
|
||||
{{>project}}
|
||||
{{else}}
|
||||
{{>version}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{{#partial projects}}
|
||||
<div class="table-responsive">
|
||||
<table class="pure-table pure-table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Project</th>
|
||||
<th>Status</th>
|
||||
<th>Last Release</th>
|
||||
<th>Last Release File</th>
|
||||
<th>Last Version</th>
|
||||
<th>Last Log</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#projects:i}}
|
||||
<tr title="{{formatDate(.lastLog.when)}}">
|
||||
<td><a href="/project/{{.id}}/">{{.name}}</a></td>
|
||||
<td>{{.status}}</td>
|
||||
<td>
|
||||
<a href="/project/{{.id}}/{{.releaseVersion}}">{{.releaseVersion}}</a>
|
||||
</td>
|
||||
<td>
|
||||
{{#if releases[.id]}}
|
||||
<a href="/release/{{.id}}?file">{{releases[id].fileName}}</a>
|
||||
{{else}}
|
||||
No release file available
|
||||
{{/if}}
|
||||
</td>
|
||||
<td>
|
||||
<a href="/project/{{.id}}/{{.lastLog.version}}">{{.lastLog.version}}</a>
|
||||
</td>
|
||||
<td title="{{.lastLog.log}}">{{#if .lastLog.log}}{{.lastLog.log.substring(0,100)}}{{/if}}</td>
|
||||
</tr>
|
||||
{{/projects}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{/partial}}
|
||||
|
||||
{{#partial project}}
|
||||
<div class="table-responsive">
|
||||
<table class="pure-table pure-table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Version</th>
|
||||
<th>Stage</th>
|
||||
<th>Last Log</th>
|
||||
<th>Release File</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#project.versions:i}}
|
||||
<tr title="{{formatDate(.when)}}">
|
||||
<td>
|
||||
<a href="/project/{{project.id}}/{{.version}}">{{.version}}</a>
|
||||
</td>
|
||||
<td>{{.stage}}</td>
|
||||
<td title="{{.log}}">{{#if .log}}{{.log.substring(0,100)}}{{/if}}</td>
|
||||
<td>
|
||||
{{#if releases[project.id + .version]}}
|
||||
<a href="/release/{{project.id}}/{{.version}}?file">{{releases[project.id + .version].fileName}}</a>
|
||||
{{/if}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/versions}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{{/partial}}
|
||||
|
||||
{{#partial version}}
|
||||
<hr>
|
||||
{{#if releases[project.id + .version]}}
|
||||
<a href="/release/{{project.id}}/{{.version}}?file" class="pull-right pure-button pure-button-primary">Download Release</a>
|
||||
{{/if}}
|
||||
|
||||
<div class="pure-menu pure-menu-horizontal">
|
||||
<ul class="pure-menu-list">
|
||||
<li class="pure-menu-item {{#if !currentStage}}pure-menu-selected{{/if}}">
|
||||
<a href="/project/{{project.id}}/{{.version}}/" class="pure-menu-link">All</a>
|
||||
</li>
|
||||
{{#stages:i}}
|
||||
<li class="pure-menu-item {{#if currentStage && currentStage == .stage}}pure-menu-selected{{/if}}">
|
||||
<a href="/project/{{project.id}}/{{version}}/{{.stage}}" class="pure-menu-link">{{.stage}}</a>
|
||||
</li>
|
||||
{{/stages}}
|
||||
</ul>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="log">
|
||||
{{#if currentStage}}
|
||||
<h3>{{currentStage}}<small class="timestamp">{{formatDate(logs.when)}}</small></h3>
|
||||
<pre><samp>{{logs.log}}</samp></pre>
|
||||
{{else}}
|
||||
{{#stages:i}}
|
||||
<h3>{{.stage}}<small class="timestamp">{{formatDate(.when)}}</small></h3>
|
||||
<pre><samp>{{.log}}</samp></pre>
|
||||
{{/stages}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/partial}}
|
||||
|
||||
</script>
|
||||
<script src="/js/ractive.min.js"></script>
|
||||
|
412
web/js/index.js
412
web/js/index.js
@ -3,17 +3,417 @@
|
||||
// that can be found in the LICENSE file.
|
||||
/* jshint strict: true */
|
||||
|
||||
Ractive.DEBUG = false;
|
||||
|
||||
(function() {
|
||||
"use strict";
|
||||
|
||||
var r = new Ractive({
|
||||
var r = new Ractive({
|
||||
el: "body",
|
||||
template: "#tMain",
|
||||
data: function() {
|
||||
return {
|
||||
data: function() {
|
||||
return {
|
||||
project: null,
|
||||
version: null,
|
||||
stages: null,
|
||||
currentStage: null,
|
||||
logs: null,
|
||||
projects: [],
|
||||
error: null,
|
||||
formatDate: formatDate,
|
||||
releases: {},
|
||||
};
|
||||
},
|
||||
decorators: {
|
||||
menu: function(node) {
|
||||
new PureDropdown(node);
|
||||
return {
|
||||
teardown: function() {
|
||||
return;
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
};
|
||||
},
|
||||
});
|
||||
setPaths();
|
||||
|
||||
|
||||
r.on({
|
||||
"triggerBuild": function(event) {
|
||||
event.original.preventDefault();
|
||||
var secret = window.prompt("Please enter the trigger secret for this project:");
|
||||
triggerBuild(r.get("project.id"), secret);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
function triggerBuild(projectID, secret) {
|
||||
ajax("POST", "/trigger/" + projectID, {
|
||||
secret: secret
|
||||
},
|
||||
function(result) {
|
||||
window.location = "/";
|
||||
},
|
||||
function(result) {
|
||||
r.set("error", err(result).message);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function setPaths() {
|
||||
var paths = window.location.pathname.split("/");
|
||||
|
||||
if (paths.length <= 1) {
|
||||
getProjects();
|
||||
return;
|
||||
}
|
||||
if (!paths[1]) {
|
||||
getProjects();
|
||||
return;
|
||||
}
|
||||
|
||||
if (paths[1] == "project") {
|
||||
if (paths[2]) {
|
||||
if (paths[3]) {
|
||||
if (paths[4]) {
|
||||
getStage(paths[2], paths[3], paths[4]);
|
||||
}
|
||||
getVersion(paths[2], paths[3]);
|
||||
}
|
||||
getProject(paths[2]);
|
||||
}
|
||||
getProjects();
|
||||
return;
|
||||
}
|
||||
|
||||
r.set("error", "Path Not found!");
|
||||
}
|
||||
|
||||
|
||||
function getProjects() {
|
||||
get("/log/",
|
||||
function(result) {
|
||||
for (var i = 0; i < result.data.length; i++) {
|
||||
setStatus(result.data[i]);
|
||||
hasRelease(result.data[i].id, "");
|
||||
}
|
||||
|
||||
result.data.sort(function(a, b) {
|
||||
if (a.name > b.name) {
|
||||
return 1;
|
||||
}
|
||||
if (a.name < b.name) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
r.set("projects", result.data);
|
||||
|
||||
window.setTimeout(getProjects, 10000);
|
||||
},
|
||||
function(result) {
|
||||
r.set("error", err(result).message);
|
||||
});
|
||||
}
|
||||
|
||||
function getProject(id) {
|
||||
get("/log/" + id,
|
||||
function(result) {
|
||||
r.set("project", result.data);
|
||||
if (result.data.versions) {
|
||||
for (var i = 0; i < result.data.versions.length; i++) {
|
||||
hasRelease(result.data.id, result.data.versions[i].version);
|
||||
}
|
||||
}
|
||||
},
|
||||
function(result) {
|
||||
r.set("error", err(result).message);
|
||||
});
|
||||
}
|
||||
|
||||
function getVersion(id, version) {
|
||||
get("/log/" + id + "/" + version,
|
||||
function(result) {
|
||||
r.set("version", version);
|
||||
r.set("stages", result.data);
|
||||
},
|
||||
function(result) {
|
||||
r.set("error", err(result).message);
|
||||
});
|
||||
}
|
||||
|
||||
function getStage(id, version, stage) {
|
||||
get("/log/" + id + "/" + version + "/" + stage,
|
||||
function(result) {
|
||||
r.set("logs", result.data);
|
||||
r.set("currentStage", stage);
|
||||
},
|
||||
function(result) {
|
||||
r.set("error", err(result).message);
|
||||
});
|
||||
}
|
||||
|
||||
function hasRelease(id, version) {
|
||||
/*/release/<project-id>/<version>*/
|
||||
get("/release/" + id + "/" + version,
|
||||
function(result) {
|
||||
r.set("releases." + id + version, result.data);
|
||||
},
|
||||
function(result) {
|
||||
r.set("releases." + id + version, undefined);
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
function setStatus(project) {
|
||||
//statuses
|
||||
if (project.stage != "waiting") {
|
||||
project.status = project.stage;
|
||||
} else if (project.lastLog.version.trim() == project.releaseVersion.trim()) {
|
||||
project.status = "Successfully Released";
|
||||
} else {
|
||||
if (project.lastLog.stage == "loading") {
|
||||
project.status = "Load Failing";
|
||||
} else if (project.lastLog.stage == "fetching") {
|
||||
project.status = "Fetch Failing";
|
||||
} else if (project.lastLog.stage == "building") {
|
||||
project.status = "Build Failing";
|
||||
} else if (project.lastLog.stage == "testing") {
|
||||
project.status = "Tests Failing";
|
||||
} else if (project.lastLog.stage == "releasing") {
|
||||
project.status = "Release Failing";
|
||||
} else {
|
||||
project.status = "Failing";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
function ajax(type, url, data, success, error) {
|
||||
"use strict";
|
||||
var req = new XMLHttpRequest();
|
||||
req.open(type, url);
|
||||
|
||||
if (success || error) {
|
||||
req.onload = function() {
|
||||
if (req.status >= 200 && req.status < 400) {
|
||||
if (success && typeof success === 'function') {
|
||||
var result;
|
||||
try {
|
||||
result = JSON.parse(req.responseText);
|
||||
} catch (e) {
|
||||
result = "";
|
||||
}
|
||||
success(result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//failed
|
||||
if (error && typeof error === 'function') {
|
||||
error(req);
|
||||
}
|
||||
};
|
||||
req.onerror = function() {
|
||||
if (error && typeof error === 'function') {
|
||||
error(req);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var sendData;
|
||||
if (type != "get") {
|
||||
req.setRequestHeader("Content-Type", "application/json");
|
||||
sendData = JSON.stringify(data);
|
||||
}
|
||||
|
||||
req.send(sendData);
|
||||
}
|
||||
|
||||
function get(url, success, error) {
|
||||
"use strict";
|
||||
ajax("GET", url, null, success, error);
|
||||
}
|
||||
|
||||
function err(response) {
|
||||
"use strict";
|
||||
var error = {
|
||||
message: "An error occurred",
|
||||
};
|
||||
|
||||
if (typeof response === "string") {
|
||||
error.message = response;
|
||||
} else {
|
||||
error.message = JSON.parse(response.responseText).message;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
function formatDate(strDate) {
|
||||
"use strict";
|
||||
var date = new Date(strDate);
|
||||
if (!date) {
|
||||
return "";
|
||||
}
|
||||
return date.toLocaleDateString() + " at " + date.toLocaleTimeString();
|
||||
}
|
||||
|
||||
|
||||
function PureDropdown(dropdownParent) {
|
||||
"use strict";
|
||||
|
||||
var PREFIX = 'pure-',
|
||||
ACTIVE_CLASS_NAME = PREFIX + 'menu-active',
|
||||
ARIA_ROLE = 'role',
|
||||
ARIA_HIDDEN = 'aria-hidden',
|
||||
MENU_OPEN = 0,
|
||||
MENU_CLOSED = 1,
|
||||
MENU_PARENT_CLASS_NAME = 'pure-menu-has-children',
|
||||
MENU_ACTIVE_SELECTOR = '.pure-menu-active',
|
||||
MENU_LINK_SELECTOR = '.pure-menu-link',
|
||||
MENU_SELECTOR = '.pure-menu-children',
|
||||
DISMISS_EVENT = (window.hasOwnProperty &&
|
||||
window.hasOwnProperty('ontouchstart')) ?
|
||||
'touchstart' : 'mousedown',
|
||||
|
||||
ARROW_KEYS_ENABLED = true,
|
||||
|
||||
ddm = this; // drop down menu
|
||||
|
||||
this._state = MENU_CLOSED;
|
||||
|
||||
this.show = function() {
|
||||
if (this._state !== MENU_OPEN) {
|
||||
this._dropdownParent.classList.add(ACTIVE_CLASS_NAME);
|
||||
this._menu.setAttribute(ARIA_HIDDEN, false);
|
||||
this._state = MENU_OPEN;
|
||||
}
|
||||
};
|
||||
|
||||
this.hide = function() {
|
||||
if (this._state !== MENU_CLOSED) {
|
||||
this._dropdownParent.classList.remove(ACTIVE_CLASS_NAME);
|
||||
this._menu.setAttribute(ARIA_HIDDEN, true);
|
||||
this._link.focus();
|
||||
this._state = MENU_CLOSED;
|
||||
}
|
||||
};
|
||||
|
||||
this.toggle = function() {
|
||||
this[this._state === MENU_CLOSED ? 'show' : 'hide']();
|
||||
};
|
||||
|
||||
this.halt = function(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
this._dropdownParent = dropdownParent;
|
||||
this._link = this._dropdownParent.querySelector(MENU_LINK_SELECTOR);
|
||||
this._menu = this._dropdownParent.querySelector(MENU_SELECTOR);
|
||||
this._firstMenuLink = this._menu.querySelector(MENU_LINK_SELECTOR);
|
||||
|
||||
// Set ARIA attributes
|
||||
this._link.setAttribute('aria-haspopup', 'true');
|
||||
this._menu.setAttribute(ARIA_ROLE, 'menu');
|
||||
this._menu.setAttribute('aria-labelledby', this._link.getAttribute('id'));
|
||||
this._menu.setAttribute('aria-hidden', 'true');
|
||||
[].forEach.call(
|
||||
this._menu.querySelectorAll('li'),
|
||||
function(el) {
|
||||
el.setAttribute(ARIA_ROLE, 'presentation');
|
||||
}
|
||||
);
|
||||
[].forEach.call(
|
||||
this._menu.querySelectorAll('a'),
|
||||
function(el) {
|
||||
el.setAttribute(ARIA_ROLE, 'menuitem');
|
||||
}
|
||||
);
|
||||
|
||||
// Toggle on click
|
||||
this._link.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
ddm.toggle();
|
||||
});
|
||||
|
||||
// Keyboard navigation
|
||||
document.addEventListener('keydown', function(e) {
|
||||
var currentLink,
|
||||
previousSibling,
|
||||
nextSibling,
|
||||
previousLink,
|
||||
nextLink;
|
||||
|
||||
// if the menu isn't active, ignore
|
||||
if (ddm._state !== MENU_OPEN) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if the menu is the parent of an open, active submenu, ignore
|
||||
if (ddm._menu.querySelector(MENU_ACTIVE_SELECTOR)) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentLink = ddm._menu.querySelector(':focus');
|
||||
|
||||
// Dismiss an open menu on ESC
|
||||
if (e.keyCode === 27) {
|
||||
/* Esc */
|
||||
ddm.halt(e);
|
||||
ddm.hide();
|
||||
}
|
||||
// Go to the next link on down arrow
|
||||
else if (ARROW_KEYS_ENABLED && e.keyCode === 40) {
|
||||
/* Down arrow */
|
||||
ddm.halt(e);
|
||||
// get the nextSibling (an LI) of the current link's LI
|
||||
nextSibling = (currentLink) ? currentLink.parentNode.nextSibling : null;
|
||||
// if the nextSibling is a text node (not an element), go to the next one
|
||||
while (nextSibling && nextSibling.nodeType !== 1) {
|
||||
nextSibling = nextSibling.nextSibling;
|
||||
}
|
||||
nextLink = (nextSibling) ? nextSibling.querySelector('.pure-menu-link') : null;
|
||||
// if there is no currently focused link, focus the first one
|
||||
if (!currentLink) {
|
||||
ddm._menu.querySelector('.pure-menu-link').focus();
|
||||
} else if (nextLink) {
|
||||
nextLink.focus();
|
||||
}
|
||||
}
|
||||
// Go to the previous link on up arrow
|
||||
else if (ARROW_KEYS_ENABLED && e.keyCode === 38) {
|
||||
/* Up arrow */
|
||||
ddm.halt(e);
|
||||
// get the currently focused link
|
||||
previousSibling = (currentLink) ? currentLink.parentNode.previousSibling : null;
|
||||
while (previousSibling && previousSibling.nodeType !== 1) {
|
||||
previousSibling = previousSibling.previousSibling;
|
||||
}
|
||||
previousLink = (previousSibling) ? previousSibling.querySelector('.pure-menu-link') : null;
|
||||
// if there is no currently focused link, focus the last link
|
||||
if (!currentLink) {
|
||||
ddm._menu.querySelector('.pure-menu-item:last-child .pure-menu-link').focus();
|
||||
}
|
||||
// else if there is a previous item, go to the previous item
|
||||
else if (previousLink) {
|
||||
previousLink.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Dismiss an open menu on outside event
|
||||
document.addEventListener(DISMISS_EVENT, function(e) {
|
||||
var target = e.target;
|
||||
if (target !== ddm._link && !ddm._menu.contains(target)) {
|
||||
ddm.hide();
|
||||
ddm._link.blur();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
@ -10,6 +10,8 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.townsourced.com/ironsmith/datastore"
|
||||
)
|
||||
|
||||
// /path/<project-id>/<version>/<stage>
|
||||
@ -73,9 +75,21 @@ func logGet(w http.ResponseWriter, r *http.Request) {
|
||||
if errHandled(err, w, r) {
|
||||
return
|
||||
}
|
||||
|
||||
prjData, err := project.webData()
|
||||
if errHandled(err, w, r) {
|
||||
return
|
||||
}
|
||||
|
||||
respondJsend(w, &JSend{
|
||||
Status: statusSuccess,
|
||||
Data: vers,
|
||||
Data: struct {
|
||||
*webProject
|
||||
Versions []*datastore.Log `json:"versions"`
|
||||
}{
|
||||
webProject: prjData,
|
||||
Versions: vers,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -226,6 +240,11 @@ func triggerPost(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if strings.TrimSpace(project.TriggerSecret) == "" {
|
||||
four04(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
input := &triggerInput{}
|
||||
if errHandled(parseInput(r, input), w, r) {
|
||||
return
|
||||
|
Reference in New Issue
Block a user