moved to single binary project structure

This commit is contained in:
Brad Rydzewski 2015-09-29 17:53:21 -07:00
parent 76ea19aca0
commit 155576fb03
139 changed files with 0 additions and 9815 deletions

View file

@ -1 +0,0 @@
This is where Drone binaries go after running "make" in the root directory.

View file

@ -1,261 +0,0 @@
package main
import (
"html/template"
"net/http"
"github.com/drone/drone/Godeps/_workspace/src/github.com/namsral/flag"
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin"
"github.com/drone/drone/Godeps/_workspace/src/github.com/elazarl/go-bindata-assetfs"
"github.com/drone/drone/pkg/remote"
"github.com/drone/drone/pkg/server"
log "github.com/drone/drone/Godeps/_workspace/src/github.com/Sirupsen/logrus"
eventbus "github.com/drone/drone/pkg/bus/builtin"
queue "github.com/drone/drone/pkg/queue/builtin"
runner "github.com/drone/drone/pkg/runner/builtin"
"github.com/drone/drone/pkg/store"
_ "github.com/drone/drone/pkg/remote/builtin/github"
_ "github.com/drone/drone/pkg/remote/builtin/gitlab"
_ "github.com/drone/drone/pkg/store/builtin"
)
var (
// commit sha for the current build, set by
// the compile process.
version string
revision string
)
var conf = struct {
debug bool
server struct {
addr string
cert string
key string
}
docker struct {
host string
cert string
key string
ca string
}
remote struct {
driver string
config string
}
database struct {
driver string
config string
}
plugin struct {
filter string
}
}{}
func main() {
flag.StringVar(&conf.docker.host, "docker-host", "unix:///var/run/docker.sock", "")
flag.StringVar(&conf.docker.cert, "docker-cert", "", "")
flag.StringVar(&conf.docker.key, "docker-key", "", "")
flag.StringVar(&conf.docker.ca, "docker-ca", "", "")
flag.StringVar(&conf.server.addr, "server-addr", ":8080", "")
flag.StringVar(&conf.server.cert, "server-cert", "", "")
flag.StringVar(&conf.server.key, "server-key", "", "")
flag.StringVar(&conf.remote.driver, "remote-driver", "github", "")
flag.StringVar(&conf.remote.config, "remote-config", "https://github.com", "")
flag.StringVar(&conf.database.driver, "database-driver", "sqlite3", "")
flag.StringVar(&conf.database.config, "database-config", "drone.sqlite", "")
flag.StringVar(&conf.plugin.filter, "plugin-filter", "plugins/*", "")
flag.BoolVar(&conf.debug, "debug", false, "")
flag.String("config", "", "")
flag.Parse()
store, err := store.New(conf.database.driver, conf.database.config)
if err != nil {
panic(err)
}
remote, err := remote.New(conf.remote.driver, conf.remote.config)
if err != nil {
panic(err)
}
eventbus_ := eventbus.New()
queue_ := queue.New()
updater := runner.NewUpdater(eventbus_, store, remote)
runner_ := runner.Runner{Updater: updater}
// launch the local queue runner if the system
// is not conifugred to run in agent mode
go run(&runner_, queue_)
r := gin.Default()
api := r.Group("/api")
api.Use(server.SetHeaders())
api.Use(server.SetBus(eventbus_))
api.Use(server.SetDatastore(store))
api.Use(server.SetRemote(remote))
api.Use(server.SetQueue(queue_))
api.Use(server.SetUser())
api.Use(server.SetRunner(&runner_))
api.OPTIONS("/*path", func(c *gin.Context) {})
user := api.Group("/user")
{
user.Use(server.MustUser())
user.GET("", server.GetUserCurr)
user.PATCH("", server.PutUserCurr)
user.GET("/feed", server.GetUserFeed)
user.GET("/repos", server.GetUserRepos)
user.POST("/token", server.PostUserToken)
}
users := api.Group("/users")
{
users.Use(server.MustAdmin())
users.GET("", server.GetUsers)
users.GET("/:name", server.GetUser)
users.POST("/:name", server.PostUser)
users.PATCH("/:name", server.PutUser)
users.DELETE("/:name", server.DeleteUser)
}
repos := api.Group("/repos/:owner/:name")
{
repos.POST("", server.PostRepo)
repo := repos.Group("")
{
repo.Use(server.SetRepo())
repo.Use(server.SetPerm())
repo.Use(server.CheckPull())
repo.Use(server.CheckPush())
repo.GET("", server.GetRepo)
repo.PATCH("", server.PutRepo)
repo.DELETE("", server.DeleteRepo)
repo.POST("/encrypt", server.Encrypt)
repo.POST("/watch", server.Subscribe)
repo.DELETE("/unwatch", server.Unsubscribe)
repo.GET("/builds", server.GetBuilds)
repo.GET("/builds/:number", server.GetBuild)
repo.POST("/builds/:number", server.RunBuild)
repo.DELETE("/builds/:number", server.KillBuild)
repo.GET("/logs/:number/:task", server.GetLogs)
// repo.POST("/status/:number", server.PostBuildStatus)
}
}
badges := api.Group("/badges/:owner/:name")
{
badges.Use(server.SetRepo())
badges.GET("/status.svg", server.GetBadge)
badges.GET("/cc.xml", server.GetCC)
}
hooks := api.Group("/hook")
{
hooks.POST("", server.PostHook)
}
stream := api.Group("/stream")
{
stream.Use(server.SetRepo())
stream.Use(server.SetPerm())
stream.GET("/:owner/:name", server.GetRepoEvents)
stream.GET("/:owner/:name/:build/:number", server.GetStream)
}
auth := r.Group("/authorize")
{
auth.Use(server.SetHeaders())
auth.Use(server.SetDatastore(store))
auth.Use(server.SetRemote(remote))
auth.GET("", server.GetLogin)
auth.POST("", server.GetLogin)
}
gitlab := r.Group("/gitlab/:owner/:name")
{
gitlab.Use(server.SetDatastore(store))
gitlab.Use(server.SetRepo())
gitlab.GET("/commits/:sha", server.GetCommit)
gitlab.GET("/pulls/:number", server.GetPullRequest)
redirects := gitlab.Group("/redirect")
{
redirects.GET("/commits/:sha", server.RedirectSha)
redirects.GET("/pulls/:number", server.RedirectPullRequest)
}
}
r.SetHTMLTemplate(index())
r.NoRoute(func(c *gin.Context) {
c.HTML(200, "index.html", nil)
})
http.Handle("/static/", static())
http.Handle("/", r)
if len(conf.server.cert) == 0 {
err = http.ListenAndServe(conf.server.addr, nil)
} else {
err = http.ListenAndServeTLS(conf.server.addr, conf.server.cert, conf.server.key, nil)
}
if err != nil {
log.Error("Cannot start server: ", err)
}
}
// static is a helper function that will setup handlers
// for serving static files.
func static() http.Handler {
// default file server is embedded
var handler = http.FileServer(&assetfs.AssetFS{
Asset: Asset,
AssetDir: AssetDir,
Prefix: "cmd/drone-server/static",
})
if conf.debug {
handler = http.FileServer(
http.Dir("cmd/drone-server/static"),
)
}
return http.StripPrefix("/static/", handler)
}
// index is a helper function that will setup a template
// for rendering the main angular index.html file.
func index() *template.Template {
file := MustAsset("cmd/drone-server/static/index.html")
filestr := string(file)
return template.Must(template.New("index.html").Parse(filestr))
}
// run is a helper function for initializing the
// built-in build runner, if not running in remote
// mode.
func run(r *runner.Runner, q *queue.Queue) {
defer func() {
recover()
}()
r.Poll(q)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 557 B

View file

@ -1,93 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="43"
height="36.350704"
id="svg2"
version="1.1"
inkscape:version="0.48.3.1 r9886"
sodipodi:docname="drone-logo-no-circle.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#424242"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2.8"
inkscape:cx="26.576205"
inkscape:cy="-72.54425"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:snap-global="false"
inkscape:window-width="1295"
inkscape:window-height="744"
inkscape:window-x="65"
inkscape:window-y="24"
inkscape:window-maximized="1"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0">
<inkscape:grid
type="xygrid"
id="grid2996"
empspacing="5"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true"
originx="-21.720779px"
originy="-990.37188px" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-21.720779,-25.639593)">
<path
sodipodi:type="arc"
style="fill:#424242;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="path2998"
sodipodi:cx="172.10474"
sodipodi:cy="458.39249"
sodipodi:rx="5.4295697"
sodipodi:ry="5.0507627"
d="m 177.53431,458.39249 c 0,2.78946 -2.43091,5.05076 -5.42957,5.05076 -2.99867,0 -5.42957,-2.2613 -5.42957,-5.05076 0,-2.78946 2.4309,-5.05077 5.42957,-5.05077 2.99866,0 5.42957,2.26131 5.42957,5.05077 z"
transform="matrix(1.0129716,0,0,1.0889445,-131.11643,-452.42373)" />
<path
style="fill:#424242;fill-opacity:1;stroke-width:0;stroke-miterlimit:4"
d="m 43.220779,25.640247 c 9.60163,0.0752 20.51786,6.8438 21.5,19.6 l -13,0 c 0,0 -1.67472,-7.04733 -8.5,-7 -6.82528,0.0473 -8.5,7 -8.5,7 l -13,0 c 0.63161,-12.53073 11.36576,-19.67935 21.5,-19.6 z"
id="rect3810"
inkscape:connector-curvature="0"
sodipodi:nodetypes="scczccs" />
<path
style="fill:#424242;fill-opacity:1;stroke-width:0;stroke-miterlimit:4"
d="m 43.310069,61.990247 c -7.159395,0.01905 -13.847588,-5.383347 -16.58929,-13.75 l 8,0 c 0,0 1.72575,6.96782 8.55103,6.92049 6.82528,-0.0473 8.44897,-6.92049 8.44897,-6.92049 l 8,0 c -1.783351,8.850973 -9.251314,13.730946 -16.41071,13.75 z"
id="rect3810-1"
inkscape:connector-curvature="0"
sodipodi:nodetypes="scczccs" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

View file

@ -1,46 +0,0 @@
<!DOCTYPE html>
<html ng-app="drone" lang="en">
<head>
<base href="/"/>
<link rel="stylesheet" href="/static/styles/reset.css" />
<link rel="stylesheet" href="/static/styles/fonts.css" />
<link rel="stylesheet" href="/static/styles/alert.css" />
<link rel="stylesheet" href="/static/styles/blankslate.css" />
<link rel="stylesheet" href="/static/styles/list.css" />
<link rel="stylesheet" href="/static/styles/label.css" />
<link rel="stylesheet" href="/static/styles/range.css" />
<link rel="stylesheet" href="/static/styles/switch.css" />
<link rel="stylesheet" href="/static/styles/main.css" />
<link rel="icon" href="/static/favicon.png">
</head>
<body>
<div ui-view="layout"></div>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.15/angular.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.15/angular-route.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.15/angular-resource.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui/0.4.0/angular-ui.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.15/angular-ui-router.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.6.0/moment.min.js"></script>
<!-- main javascript application -->
<script src="/static/scripts/term.js"></script>
<script src="/static/scripts/drone.js"></script>
<script src="/static/scripts/controllers/repos.js"></script>
<script src="/static/scripts/controllers/builds.js"></script>
<script src="/static/scripts/controllers/users.js"></script>
<script src="/static/scripts/services/repos.js"></script>
<script src="/static/scripts/services/builds.js"></script>
<script src="/static/scripts/services/users.js"></script>
<script src="/static/scripts/services/logs.js"></script>
<script src="/static/scripts/services/tokens.js"></script>
<script src="/static/scripts/services/feed.js"></script>
<script src="/static/scripts/filters/filter.js"></script>
<script src="/static/scripts/filters/gravatar.js"></script>
<script src="/static/scripts/filters/time.js"></script>
</body>
</html>

View file

@ -1,245 +0,0 @@
(function () {
/**
* BuildsCtrl responsible for rendering the repo's
* recent build history.
*/
function BuildsCtrl($scope, $stateParams, builds, repos, users, logs) {
var owner = $stateParams.owner;
var name = $stateParams.name;
var fullName = owner + '/' + name;
$scope.loading=true;
// Gets the currently authenticated user
// users.getCached().then(function (payload) {
// $scope.user = payload.data;
// });
// Gets a repository
repos.get(fullName).then(function (payload) {
$scope.repo = payload.data;
$scope.loading=false;
}).catch(function (err) {
$scope.error = err;
});
// Gets a list of builds
builds.list(fullName).then(function (payload) {
$scope.builds = angular.isArray(payload.data) ? payload.data : [];
}).catch(function (err) {
$scope.error = err;
});
$scope.watch = function (repo) {
repos.watch(repo.full_name).then(function (payload) {
$scope.repo.starred = true;
});
};
$scope.unwatch = function (repo) {
repos.unwatch(repo.full_name).then(function () {
$scope.repo.starred = false;
});
};
repos.subscribe(fullName, function (event) {
var added = false;
for (var i = 0; i < $scope.builds.length; i++) {
var build = $scope.builds[i];
if (event.number !== build.number) {
continue; // ignore
}
// update the build status
$scope.builds[i] = event;
$scope.$apply();
added = true;
}
if (!added) {
$scope.builds.push(event);
$scope.$apply();
}
});
}
/**
* BuildCtrl responsible for rendering a build.
*/
function BuildCtrl($scope, $stateParams, $window, logs, builds, repos, users) {
var number = $stateParams.number;
var owner = $stateParams.owner;
var name = $stateParams.name;
var fullName = owner + '/' + name;
var step = parseInt($stateParams.step) || 1;
// Gets the currently authenticated user
users.getCached().then(function (payload) {
$scope.user = payload.data;
});
// Gets a repository
repos.get(fullName).then(function (payload) {
$scope.repo = payload.data;
}).catch(function (err) {
$scope.error = err;
});
// Gets the build
builds.get(fullName, number).then(function (payload) {
$scope.step = step;
$scope.build = payload.data;
}).catch(function (err) {
$scope.error = err;
});
repos.subscribe(fullName, function (event) {
if (event.number !== parseInt(number)) {
return; // ignore
}
// update the build
$scope.build = event;
$scope.$apply();
});
$scope.restart = function () {
builds.restart(fullName, number).then(function (payload) {
$scope.build = payload.data;
}).catch(function (err) {
$scope.error = err;
});
};
$scope.cancel = function () {
builds.cancel(fullName, number).then(function (payload) {
$scope.build = payload.data;
}).catch(function (err) {
$scope.error = err;
});
};
$scope.tail = function () {
tail = !tail;
};
}
/**
* BuildOutCtrl responsible for rendering a build output.
*/
function BuildOutCtrl($scope, $stateParams, $window, logs, builds, repos, users) {
var step = parseInt($stateParams.step) || 1;
var number = $stateParams.number;
var owner = $stateParams.owner;
var name = $stateParams.name;
var fullName = owner + '/' + name;
var streaming = false;
var tail = false;
// Initiates streaming a build.
var stream = function () {
if (streaming) {
return;
}
streaming = true;
var convert = new Filter({stream: true, newline: false});
var term = document.getElementById("term");
term.innerHTML = "";
// subscribes to the build otuput.
logs.subscribe(fullName, number, step, function (data) {
term.innerHTML += convert.toHtml(data.replace("\\n", "\n"));
if (tail) {
// scrolls to the bottom of the page if enabled
$window.scrollTo(0, $window.document.body.scrollHeight);
}
});
};
// Gets the currently authenticated user
// users.getCached().then(function (payload) {
// $scope.user = payload.data;
// });
// Gets a repository
repos.get(fullName).then(function (payload) {
$scope.repo = payload.data;
}).catch(function (err) {
$scope.error = err;
});
// Gets the build
builds.get(fullName, number).then(function (payload) {
$scope.build = payload.data;
$scope.task = payload.data.jobs[step - 1];
$scope.step = step;
if (['pending', 'killed'].indexOf($scope.task.status) !== -1) {
// do nothing
} else if ($scope.task.status === 'running') {
// stream the build
stream();
} else {
// fetch the logs for the finished build.
logs.get(fullName, number, step).then(function (payload) {
var convert = new Filter({stream: false, newline: false});
var term = document.getElementById("term")
term.innerHTML = convert.toHtml(payload.data);
}).catch(function (err) {
$scope.error = err;
});
}
}).catch(function (err) {
$scope.error = err;
});
repos.subscribe(fullName, function (event) {
if (event.number !== parseInt(number)) {
return; // ignore
}
// update the build
$scope.build = event;
$scope.task = event.jobs[step - 1];
$scope.$apply();
// start streaming the current build
if ($scope.task.status === 'running') {
stream();
} else {
// resets our streaming state
streaming = false;
}
});
$scope.restart = function () {
builds.restart(fullName, number).then(function (payload) {
$scope.build = payload.data;
$scope.task = payload.data.jobs[step - 1];
}).catch(function (err) {
$scope.error = err;
});
};
$scope.cancel = function () {
builds.cancel(fullName, number).then(function (payload) {
$scope.build = payload.data;
$scope.task = payload.data.builds[step - 1];
}).catch(function (err) {
$scope.error = err;
});
};
$scope.tail = function () {
tail = !tail;
};
}
angular
.module('drone')
.controller('BuildOutCtrl', BuildOutCtrl)
.controller('BuildCtrl', BuildCtrl)
.controller('BuildsCtrl', BuildsCtrl);
})();

View file

@ -1,67 +0,0 @@
(function () {
/**
* CommitsCtrl responsible for rendering the repo's
* recent commit history.
*/
function CommitsCtrl($scope, $stateParams, builds, repos, users, logs) {
var owner = $stateParams.owner;
var name = $stateParams.name;
var fullName = owner+'/'+name;
// Gets the currently authenticated user
users.getCached().then(function(payload){
$scope.user = payload.data;
});
// Gets a repository
repos.get(fullName).then(function(payload){
$scope.repo = payload.data;
}).catch(function(err){
$scope.error = err;
});
// Gets a list of commits
builds.list(fullName).then(function(payload){
$scope.builds = angular.isArray(payload.data) ? payload.data : [];
}).catch(function(err){
$scope.error = err;
});
$scope.watch = function(repo) {
repos.watch(repo.full_name).then(function(payload) {
$scope.repo.starred = true;
});
}
$scope.unwatch = function(repo) {
repos.unwatch(repo.full_name).then(function() {
$scope.repo.starred = false;
});
}
repos.subscribe(fullName, function(event) {
var added = false;
for (var i=0;i<$scope.builds.length;i++) {
var build = $scope.builds[i];
if (event.number !== build.number) {
continue; // ignore
}
// update the build status
$scope.builds[i] = event;
$scope.$apply();
added = true;
}
if (!added) {
$scope.builds.push(event);
$scope.$apply();
}
});
}
angular
.module('drone')
.controller('CommitsCtrl', CommitsCtrl)
})();

View file

@ -1,150 +0,0 @@
(function () {
/**
* ReposCtrl responsible for rendering the user's
* repository home screen.
*/
function ReposCtrl($scope, $location, $stateParams, repos, users) {
$scope.loading = true;
$scope.waiting = false;
// Gets the currently authenticated user
users.getCached().then(function (payload) {
$scope.user = payload.data;
$scope.loading = false;
});
// Gets a list of repos to display in the
// dropdown.
repos.list().then(function (payload) {
$scope.repos = angular.isArray(payload.data) ? payload.data : [];
}).catch(function (err) {
$scope.error = err;
});
// Adds a repository
$scope.add = function (event, fullName) {
$scope.error = undefined;
if (event.which && event.which !== 13) {
return;
}
$scope.waiting = true;
repos.post(fullName).then(function (payload) {
$location.path('/' + fullName);
$scope.waiting = false;
}).catch(function (err) {
$scope.error = err;
$scope.waiting = false;
$scope.search_text = undefined;
});
}
}
/**
* RepoAddCtrl responsible for activaing a new
* repository.
*/
function RepoAddCtrl($scope, $location, repos, users) {
// Gets the currently authenticated user
users.getCached().then(function (payload) {
$scope.user = payload.data;
});
$scope.add = function (slug) {
repos.post(slug).then(function (payload) {
$location.path('/' + slug);
}).catch(function (err) {
$scope.error = err;
});
}
}
/**
* RepoEditCtrl responsible for editing a repository.
*/
function RepoEditCtrl($scope, $window, $location, $stateParams, repos, users) {
var owner = $stateParams.owner;
var name = $stateParams.name;
var fullName = owner + '/' + name;
// Inject window for composing url
$scope.window = $window;
// Gets the currently authenticated user
users.getCached().then(function (payload) {
$scope.user = payload.data;
});
// Gets a repository
repos.get(fullName).then(function (payload) {
$scope.repo = payload.data;
}).catch(function (err) {
$scope.error = err;
});
$scope.save = function (repo) {
repo.timeout = parseInt(repo.timeout);
repos.update(repo).then(function (payload) {
$scope.repo = payload.data;
}).catch(function (err) {
$scope.error = err;
});
};
$scope.delete = function (repo) {
repos.delete(repo).then(function (payload) {
$location.path('/');
}).catch(function (err) {
$scope.error = err;
});
};
$scope.param = {};
$scope.addParam = function (param) {
if (!$scope.repo.params) {
$scope.repo.params = {}
}
$scope.repo.params[param.key] = param.value;
$scope.param = {};
// auto-update
repos.update($scope.repo).then(function (payload) {
$scope.repo = payload.data;
}).catch(function (err) {
$scope.error = err;
});
};
$scope.encrypt = function (plaintext) {
repos.encrypt(fullName, plaintext).then(function (payload) {
$scope.secure = payload.data;
}).catch(function (err) {
$scope.error = err;
});
};
$scope.deleteParam = function (key) {
delete $scope.repo.params[key];
// auto-update
repos.update($scope.repo).then(function (payload) {
$scope.repo = payload.data;
}).catch(function (err) {
$scope.error = err;
});
}
}
function toSnakeCase(str) {
return str.replace(/ /g, '_').replace(/([a-z0-9])([A-Z0-9])/g, '$1_$2').toLowerCase();
}
angular
.module('drone')
.controller('ReposCtrl', ReposCtrl)
.controller('RepoAddCtrl', RepoAddCtrl)
.controller('RepoEditCtrl', RepoEditCtrl);
})();

View file

@ -1,119 +0,0 @@
(function () {
function UserHeaderCtrl($scope, $stateParams, users) {
// Gets the currently authenticated user
users.getCurrent().then(function(payload){
$scope.user = payload.data;
});
$scope.number = $stateParams.number || undefined;
$scope.owner = $stateParams.owner || undefined;
$scope.name = $stateParams.name || undefined;
$scope.full_name = $scope.owner + '/' + $scope.name;
}
function UserLoginCtrl($scope, $window) {
// attempts to extract an error message from
// the URL hash in format #error=?
$scope.error = $window.location.hash.substr(7);
}
function UserLogoutCtrl($scope, $window, $state) {
// Remove login information from the local
// storage and redirect to login page
if (localStorage.hasOwnProperty("access_token")) {
localStorage.removeItem("access_token");
}
$state.go("login", {}, {
location: "replace"
});
}
/**
* UserCtrl is responsible for managing user settings.
*/
function UserCtrl($scope, users, tokens) {
// Gets the currently authenticated user
users.getCurrent().then(function(payload){
$scope.user = payload.data;
});
$scope.showToken = function() {
tokens.post().then(function(payload) {
$scope.token = payload.data;
});
}
}
/**
* UsersCtrl is responsible for managing user accounts.
* This part of the site is for administrators only.
*/
function UsersCtrl($scope, users) {
$scope.loading = true;
$scope.waiting = false;
// Gets the currently authenticated user
users.getCached().then(function(payload){
$scope.user = payload.data;
});
// Gets the list of all system users
users.list().then(function(payload){
$scope.loading = true;
$scope.users = payload.data;
});
$scope.add = function(event, login) {
$scope.error = undefined;
$scope.new_user = undefined;
if (event.which && event.which !== 13) {
return;
}
$scope.waiting = true;
users.post(login).then(function(payload){
$scope.users.push(payload.data);
$scope.search_text=undefined;
$scope.waiting = false;
$scope.new_user = payload.data;
}).catch(function (err) {
$scope.error = err;
$scope.waiting = false;
$scope.search_text = undefined;
});
}
$scope.toggle = function(user) {
if (user.login === $scope.user.login) {
// cannot revoke admin privilege for self
$scope.error = {}; // todo display an actual error here
return;
}
user.admin = !user.admin;
users.put(user);
}
$scope.remove = function(user) {
if (user.login === $scope.user.login) {
// cannot delete self
$scope.error = {}; // todo display an actual error here
return;
}
users.delete(user).then(function(){
var index = $scope.users.indexOf(user);
$scope.users.splice(index, 1);
});
}
}
angular
.module('drone')
.controller('UserHeaderCtrl', UserHeaderCtrl)
.controller('UserLoginCtrl', UserLoginCtrl)
.controller('UserLogoutCtrl', UserLogoutCtrl)
.controller('UserCtrl', UserCtrl)
.controller('UsersCtrl', UsersCtrl);
})();

View file

@ -1,283 +0,0 @@
'use strict';
(function () {
/**
* Creates the angular application.
*/
angular.module('drone', [
'ngRoute',
'ui.filters',
'ui.router'
]);
/**
* Bootstraps the application and retrieves the
* token from the
*/
function Authorize() {
// First, parse the query string
var params = {}, queryString = location.hash.substring(1),
regex = /([^&=]+)=([^&]*)/g, m;
// Loop through and retrieve the token
while (m = regex.exec(queryString)) {
params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
}
// if the user has just received an auth token we
// should extract from the URL, save to local storage
// and then remove from the URL for good measure.
if (params.access_token) {
localStorage.setItem("access_token", params.access_token);
history.replaceState({}, document.title, location.pathname);
}
}
/**
* Defines the route configuration for the
* main application.
*/
function Config($stateProvider, $httpProvider, $locationProvider) {
// Resolver that will attempt to load the currently
// authenticated user prior to loading the page.
var resolveUser = {
user: function (users) {
return users.getCached();
}
};
$stateProvider
.state('app', {
abstract: true,
views: {
'layout': {
templateUrl: '/static/scripts/views/layout.html',
controller: function ($scope, $routeParams, repos, users) {
users.getCached().then(function (payload) {
if (payload && payload.data) {
$scope.user = payload.data;
}
});
}
}
}
})
.state('app.index', {
url: '/',
views: {
'toolbar': {
templateUrl: '/static/scripts/views/repos/index/toolbar.html'
},
'content': {
templateUrl: '/static/scripts/views/repos/index/content.html',
controller: 'ReposCtrl',
resolve: resolveUser
}
},
title: 'Dashboard'
})
.state('login', {
url: '/login',
views: {
'layout': {
templateUrl: '/static/scripts/views/login.html',
controller: 'UserLoginCtrl',
resolve: resolveUser
}
},
title: 'Login'
})
.state('logout', {
url: '/logout',
views: {
'layout': {
templateUrl: '/static/scripts/views/login.html',
controller: 'UserLogoutCtrl',
resolve: resolveUser
}
},
title: 'Logout'
})
.state('app.profile', {
url: '/profile',
views: {
'toolbar': {templateUrl: '/static/scripts/views/profile/toolbar.html'},
'content': {
templateUrl: '/static/scripts/views/profile/content.html',
controller: 'UserCtrl',
resolve: resolveUser
}
},
title: 'Profile'
})
.state('app.users', {
url: '/users',
views: {
'toolbar': {templateUrl: '/static/scripts/views/users/toolbar.html'},
'content': {
templateUrl: '/static/scripts/views/users/content.html',
controller: 'UsersCtrl',
resolve: resolveUser
}
},
title: 'Users'
})
.state('app.builds', {
url: '/:owner/:name',
views: {
'toolbar': {
templateUrl: '/static/scripts/views/builds/index/toolbar.html',
controller: 'UserHeaderCtrl',
resolve: resolveUser
},
'content': {
templateUrl: '/static/scripts/views/builds/index/content.html',
controller: 'BuildsCtrl'
}
},
title: 'Builds'
})
.state('app.repo_edit', {
url: '/:owner/:name/edit',
views: {
'toolbar': {
templateUrl: '/static/scripts/views/repos/toolbar.html',
controller: 'UserHeaderCtrl',
resolve: resolveUser
},
'content': {
templateUrl: '/static/scripts/views/repos/edit.html',
controller: 'RepoEditCtrl',
resolve: resolveUser
}
},
title: 'Edit Repository'
})
.state('app.repo_del', {
url: '/:owner/:name/delete',
views: {
'toolbar': {
templateUrl: '/static/scripts/views/repos/toolbar.html',
controller: 'UserHeaderCtrl',
resolve: resolveUser
},
'content': {
templateUrl: '/static/scripts/views/repos/del.html',
controller: 'RepoEditCtrl',
resolve: resolveUser
}
},
title: 'Delete Repository'
})
.state('app.repo_env', {
url: '/:owner/:name/edit/env',
views: {
'toolbar': {
templateUrl: '/static/scripts/views/repos/toolbar.html',
controller: 'UserHeaderCtrl',
resolve: resolveUser
},
'content': {
templateUrl: '/static/scripts/views/repos/env.html',
controller: 'RepoEditCtrl',
resolve: resolveUser
}
},
title: 'Private Vars'
})
.state('app.repo_secure', {
url: '/:owner/:name/secure',
views: {
'toolbar': {
templateUrl: '/static/scripts/views/repos/toolbar.html',
controller: 'UserHeaderCtrl',
resolve: resolveUser
},
'content': {
templateUrl: '/static/scripts/views/repos/secure.html',
controller: 'RepoEditCtrl',
resolve: resolveUser
}
},
title: 'Secure Variables'
})
.state('app.build', {
url: '/:owner/:name/:number',
views: {
'toolbar': {
templateUrl: '/static/scripts/views/builds/show/toolbar.html',
controller: 'UserHeaderCtrl',
resolve: resolveUser
},
'content': {
templateUrl: '/static/scripts/views/builds/show/content.html',
controller: 'BuildOutCtrl',
resolve: resolveUser
}
},
title: 'Build'
})
.state('app.job', {
url: '/:owner/:name/:number/:step',
views: {
'toolbar': {
templateUrl: '/static/scripts/views/builds/show/toolbar.html',
controller: 'UserHeaderCtrl',
resolve: resolveUser
},
'content': {
templateUrl: '/static/scripts/views/builds/show/content.html',
controller: 'BuildOutCtrl',
resolve: resolveUser
}
},
title: 'Build'
});
// Enables html5 mode
$locationProvider.html5Mode(true);
// Appends the Bearer token to authorize every
// outbound http request.
$httpProvider.defaults.headers.common.Authorization = 'Bearer ' + localStorage.getItem('access_token');
// Intercepts every oubput http response and redirects
// the user to the logic screen if the request was rejected.
$httpProvider.interceptors.push(function ($q, $location) {
return {
'responseError': function (rejection) {
if (rejection.status === 401 && rejection.config.url !== "/api/user") {
$location.path('/login');
}
if (rejection.status === 0) {
// this happens when the app is down or
// the browser loses internet connectivity.
}
return $q.reject(rejection);
}
};
});
}
function RouteChange($rootScope, repos, logs) {
$rootScope.$on('$stateChangeStart', function () {
repos.unsubscribe();
logs.unsubscribe();
});
$rootScope.$on('$stateChangeSuccess', function (event, current) {
if (current.title) {
document.title = current.title + ' · drone';
}
});
}
angular
.module('drone')
.config(Authorize)
.config(Config)
.run(RouteChange);
})();

View file

@ -1,93 +0,0 @@
'use strict';
(function () {
function trunc() {
return function(str) {
if (str && str.length > 10) {
return str.substr(0, 10);
}
return str;
}
}
/**
* author is a helper function that return the builds
* commit or pull request author.
*/
function author() {
return function(build) {
if (!build) { return ""; }
if (!build.head_commit && !build.pull_request) { return ""; }
if (build.head_commit) { return build.head_commit.author.login || ""; }
return build.pull_request.source.author.login;
}
}
/**
* sha is a helper function that return the builds sha.
*/
function sha() {
return function(build) {
if (!build) { return ""; }
if (!build.head_commit && !build.pull_request) { return ""; }
if (build.head_commit) { return build.head_commit.sha || ""; }
return build.pull_request.source.sha;
}
}
/**
* ref is a helper function that return the builds sha.
*/
function ref() {
return function(build) {
if (!build) { return ""; }
if (!build.head_commit && !build.pull_request) { return ""; }
if (build.head_commit) { return build.head_commit.ref || ""; }
return build.pull_request.source.ref;
}
}
/**
* message is a helper function that return the builds message.
*/
function message() {
return function(build) {
if (!build) { return ""; }
if (!build.head_commit && !build.pull_request) { return ""; }
if (build.head_commit) { return build.head_commit.message || ""; }
return build.pull_request.title || "";
}
}
/**
* message is a helper function that return the build icon.
*/
function icon() {
return function(status) {
switch(status) {
case "pending":
case "running":
return "refresh";
case "failure":
return "clear";
case "success":
return "check";
case "killed":
case "error":
return "remove";
}
return "";
}
}
angular
.module('drone')
.filter('trunc', trunc)
.filter('author', author)
.filter('message', message)
.filter('sha', sha)
.filter('icon', icon)
.filter('ref', ref);
})();

View file

@ -1,32 +0,0 @@
'use strict';
(function () {
/**
* gravatar is a helper function that return the user's gravatar
* image URL given an email hash.
*/
function gravatar() {
return function(hash) {
if (!hash) { return "http://www.gravatar.com/avatar/00000000000000000000000000000000?d=mm&f=y"; }
return "https://secure.gravatar.com/avatar/"+hash+"?s=48&d=mm";
}
}
/**
* gravatarLarge is a helper function that return the user's gravatar
* image URL given an email hash.
*/
function gravatarLarge() {
return function(hash) {
if (!hash) { return "http://www.gravatar.com/avatar/00000000000000000000000000000000?d=mm&f=y"; }
return "https://secure.gravatar.com/avatar/"+hash+"?s=128&d=mm";
}
}
angular
.module('drone')
.filter('gravatar', gravatar)
.filter('gravatarLarge', gravatarLarge)
})();

View file

@ -1,45 +0,0 @@
'use strict';
(function () {
/**
* fromNow is a helper function that returns a human readable
* string for the elapsed time between the given unix date and the
* current time (ex. 10 minutes ago).
*/
function fromNow() {
return function(date) {
if (!date) {
return;
}
return moment(new Date(date*1000)).fromNow();
}
}
/**
* toDuration is a helper function that returns a human readable
* string for the given duration in seconds (ex. 1 hour and 20 minutes).
*/
function toDuration() {
return function(seconds) {
return moment.duration(seconds, "seconds").humanize();
}
}
/**
* toDate is a helper function that returns a human readable
* string gor the given unix date.
*/
function toDate() {
return function(date) {
return moment(new Date(date*1000)).format('ll');
}
}
angular
.module('drone')
.filter('fromNow', fromNow)
.filter('toDate', toDate)
.filter('toDuration', toDuration)
})();

View file

@ -1,54 +0,0 @@
'use strict';
(function () {
/**
* The BuildsService provides access to build
* data using REST API calls.
*/
function BuildService($http, $window) {
/**
* Gets a list of builds.
*
* @param {string} Name of the repository.
*/
this.list = function(repoName) {
return $http.get('/api/repos/'+repoName+'/builds');
};
/**
* Gets a build.
*
* @param {string} Name of the repository.
* @param {number} Number of the build.
*/
this.get = function(repoName, buildNumber) {
return $http.get('/api/repos/'+repoName+'/builds/'+buildNumber);
};
/**
* Restarts a build.
*
* @param {string} Name of the repository.
* @param {number} Number of the build.
*/
this.restart = function(repoName, buildNumber) {
return $http.post('/api/repos/' + repoName+'/builds/'+buildNumber);
};
/**
* Cancels a running build.
*
* @param {string} Name of the repository.
* @param {number} Number of the build.
*/
this.cancel = function(repoName, buildNumber) {
return $http.delete('/api/repos/'+repoName+'/builds/'+buildNumber);
};
}
angular
.module('drone')
.service('builds', BuildService);
})();

View file

@ -1,40 +0,0 @@
'use strict';
(function () {
function FeedService($http, $window) {
var callback,
websocket,
token = localStorage.getItem('access_token');
this.subscribe = function(_callback) {
callback = _callback;
var proto = ($window.location.protocol === 'https:' ? 'wss' : 'ws'),
route = [proto, "://", $window.location.host, '/api/stream/user?access_token=', token].join('');
websocket = new WebSocket(route);
websocket.onmessage = function (event) {
if (callback !== undefined) {
callback(angular.fromJson(event.data));
}
};
websocket.onclose = function (event) {
console.log('user websocket closed');
};
};
this.unsubscribe = function() {
callback = undefined;
if (websocket !== undefined) {
websocket.close();
websocket = undefined;
}
};
}
angular
.module('drone')
.service('feed', FeedService);
})();

View file

@ -1,58 +0,0 @@
'use strict';
(function () {
/**
* The LogService provides access to build
* log data using REST API calls.
*/
function LogService($http, $window) {
/**
* Gets a task logs.
*
* @param {string} Name of the repository.
* @param {number} Number of the build.
* @param {number} Number of the task.
*/
this.get = function(repoName, number, step) {
return $http.get('/api/repos/'+repoName+'/logs/'+number+'/'+step);
};
var callback,
events,
token = localStorage.getItem('access_token');
this.subscribe = function (repoName, number, step, _callback) {
callback = _callback;
var route = ['/api/stream/', repoName, '/', number, '/', step, '?access_token=', token].join('')
events = new EventSource(route, { withCredentials: true });
events.onmessage = function (event) {
if (callback !== undefined) {
callback(event.data);
}
};
events.onerror = function (event) {
callback = undefined;
if (events !== undefined) {
events.close();
events = undefined;
}
console.log('user event stream closed due to error.', event);
};
};
this.unsubscribe = function () {
callback = undefined;
if (events !== undefined) {
events.close();
events = undefined;
}
};
}
angular
.module('drone')
.service('logs', LogService);
})();

View file

@ -1,132 +0,0 @@
'use strict';
(function () {
/**
* The RepoService provides access to repository
* data using REST API calls.
*/
function RepoService($http, $window) {
var callback,
websocket,
token = localStorage.getItem('access_token');
/**
* Gets a list of all repositories.
*/
this.list = function () {
return $http.get('/api/user/repos');
};
/**
* Gets a repository by name.
*
* @param {string} Name of the repository.
*/
this.get = function (repoName) {
return $http.get('/api/repos/' + repoName);
};
/**
* Creates a new repository.
*
* @param {object} JSON representation of a repository.
*/
this.post = function (repoName) {
return $http.post('/api/repos/' + repoName);
};
/**
* Updates an existing repository.
*
* @param {object} JSON representation of a repository.
*/
this.update = function (repo) {
return $http.patch('/api/repos/' + repo.full_name, repo);
};
/**
* Deletes a repository.
*
* @param {string} Name of the repository.
*/
this.delete = function (repoName) {
return $http.delete('/api/repos/' + repoName);
};
/**
* Watch a repository.
*
* @param {string} Name of the repository.
*/
this.watch = function (repoName) {
return $http.post('/api/repos/' + repoName + '/watch');
};
/**
* Unwatch a repository.
*
* @param {string} Name of the repository.
*/
this.unwatch = function (repoName) {
return $http.delete('/api/repos/' + repoName + '/unwatch');
};
/**
* Encrypt the set of parameters.
*
* @param {string} Name of the repository.
* @param {string} Plaintext to encrypt.
*/
this.encrypt = function (repoName, plaintext) {
var conf = {
headers: {
'Content-Type': 'text/plain; charset=UTF-8'
}
}
return $http.post('/api/repos/' + repoName + '/encrypt', btoa(plaintext), conf);
};
var callback,
events,
token = localStorage.getItem('access_token');
/**
* Subscribes to a live update feed for a repository
*
* @param {string} Name of the repository.
*/
this.subscribe = function (repo, _callback) {
callback = _callback;
events = new EventSource("/api/stream/" + repo + "?access_token=" + token, {withCredentials: true});
events.onmessage = function (event) {
if (callback !== undefined) {
callback(angular.fromJson(event.data));
}
};
events.onerror = function (event) {
callback = undefined;
if (events !== undefined) {
events.close();
events = undefined;
}
console.log('user event stream closed due to error.', event);
};
};
this.unsubscribe = function () {
callback = undefined;
if (events !== undefined) {
events.close();
events = undefined;
}
};
}
angular
.module('drone')
.service('repos', RepoService);
})();

View file

@ -1,22 +0,0 @@
'use strict';
(function () {
/**
* The TokenService provides access to user token
* data using REST API calls.
*/
function TokenService($http, $window) {
/**
* Generates a user API token.
*/
this.post = function(token) {
return $http.post('/api/user/token');
};
}
angular
.module('drone')
.service('tokens', TokenService);
})();

View file

@ -1,88 +0,0 @@
'use strict';
(function () {
/**
* Cached user object.
*/
var _user;
/**
* The UserService provides access to useer
* data using REST API calls.
*/
function UserService($http, $q) {
/**
* Gets a list of all users.
*/
this.list = function() {
return $http.get('/api/users');
};
/**
* Gets a user by login.
*/
this.get = function(login) {
return $http.get('/api/users/'+login);
};
/**
* Gets the currently authenticated user.
*/
this.getCurrent = function() {
return $http.get('/api/user');
};
/**
* Updates an existing user
*/
this.post = function(user) {
return $http.post('/api/users/'+user);
};
/**
* Updates an existing user
*/
this.put = function(user) {
return $http.patch('/api/users/'+user.login, user);
};
/**
* Deletes a user.
*/
this.delete = function(user) {
return $http.delete('/api/users/'+user.login);
};
/**
* Gets the currently authenticated user from
* the local cache. If not exists, it will fetch
* from the server.
*/
this.getCached = function() {
var defer = $q.defer();
// if the user is already authenticated
if (_user) {
defer.resolve(_user);
return defer.promise;
}
// else fetch the currently authenticated
// user using the REST API.
this.getCurrent().then(function(payload){
_user=payload;
defer.resolve(_user);
}).catch(function(){
defer.resolve(_user);
});
return defer.promise;
}
}
angular
.module('drone')
.service('users', UserService);
})();

View file

@ -1,390 +0,0 @@
var Filter, STYLES, defaults, entities, extend, toHexString, _i, _results,
__slice = [].slice;
STYLES = {
'ef0': 'color:#000',
'ef1': 'color:#A00',
'ef2': 'color:#0A0',
'ef3': 'color:#A50',
'ef4': 'color:#00A',
'ef5': 'color:#A0A',
'ef6': 'color:#0AA',
'ef7': 'color:#AAA',
'ef8': 'color:#555',
'ef9': 'color:#F55',
'ef10': 'color:#5F5',
'ef11': 'color:#FF5',
'ef12': 'color:#55F',
'ef13': 'color:#F5F',
'ef14': 'color:#5FF',
'ef15': 'color:#FFF',
'eb0': 'background-color:#000',
'eb1': 'background-color:#A00',
'eb2': 'background-color:#0A0',
'eb3': 'background-color:#A50',
'eb4': 'background-color:#00A',
'eb5': 'background-color:#A0A',
'eb6': 'background-color:#0AA',
'eb7': 'background-color:#AAA',
'eb8': 'background-color:#555',
'eb9': 'background-color:#F55',
'eb10': 'background-color:#5F5',
'eb11': 'background-color:#FF5',
'eb12': 'background-color:#55F',
'eb13': 'background-color:#F5F',
'eb14': 'background-color:#5FF',
'eb15': 'background-color:#FFF'
};
toHexString = function(num) {
num = num.toString(16);
while (num.length < 2) {
num = "0" + num;
}
return num;
};
[0, 1, 2, 3, 4, 5].forEach(function(red) {
return [0, 1, 2, 3, 4, 5].forEach(function(green) {
return [0, 1, 2, 3, 4, 5].forEach(function(blue) {
var b, c, g, n, r, rgb;
c = 16 + (red * 36) + (green * 6) + blue;
r = red > 0 ? red * 40 + 55 : 0;
g = green > 0 ? green * 40 + 55 : 0;
b = blue > 0 ? blue * 40 + 55 : 0;
rgb = ((function() {
var _i, _len, _ref, _results;
_ref = [r, g, b];
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
n = _ref[_i];
_results.push(toHexString(n));
}
return _results;
})()).join('');
STYLES["ef" + c] = "color:#" + rgb;
return STYLES["eb" + c] = "background-color:#" + rgb;
});
});
});
(function() {
_results = [];
for (_i = 0; _i <= 23; _i++){ _results.push(_i); }
return _results;
}).apply(this).forEach(function(gray) {
var c, l;
c = gray + 232;
l = toHexString(gray * 10 + 8);
STYLES["ef" + c] = "color:#" + l + l + l;
return STYLES["eb" + c] = "background-color:#" + l + l + l;
});
extend = function() {
var dest, k, obj, objs, v, _j, _len;
dest = arguments[0], objs = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
for (_j = 0, _len = objs.length; _j < _len; _j++) {
obj = objs[_j];
for (k in obj) {
v = obj[k];
dest[k] = v;
}
}
return dest;
};
defaults = {
fg: '#FFF',
bg: '#000',
newline: false,
escapeXML: false,
stream: false
};
Filter = (function() {
function Filter(options) {
if (options == null) {
options = {};
}
this.opts = extend({}, defaults, options);
this.input = [];
this.stack = [];
this.stickyStack = [];
}
Filter.prototype.toHtml = function(input) {
var buf;
this.input = typeof input === 'string' ? [input] : input;
buf = [];
this.stickyStack.forEach((function(_this) {
return function(element) {
return _this.generateOutput(element.token, element.data, function(chunk) {
return buf.push(chunk);
});
};
})(this));
this.forEach(function(chunk) {
return buf.push(chunk);
});
this.input = [];
return buf.join('');
};
Filter.prototype.forEach = function(callback) {
var buf;
buf = '';
this.input.forEach((function(_this) {
return function(chunk) {
buf += chunk;
return _this.tokenize(buf, function(token, data) {
_this.generateOutput(token, data, callback);
if (_this.opts.stream) {
return _this.updateStickyStack(token, data);
}
});
};
})(this));
if (this.stack.length) {
return callback(this.resetStyles());
}
};
Filter.prototype.generateOutput = function(token, data, callback) {
switch (token) {
case 'text':
return callback(this.pushText(data));
case 'display':
return this.handleDisplay(data, callback);
case 'xterm256':
return callback(this.pushStyle("ef" + data));
}
};
Filter.prototype.updateStickyStack = function(token, data) {
var notCategory;
notCategory = function(category) {
return function(e) {
return (category === null || e.category !== category) && category !== 'all';
};
};
if (token !== 'text') {
this.stickyStack = this.stickyStack.filter(notCategory(this.categoryForCode(data)));
return this.stickyStack.push({
token: token,
data: data,
category: this.categoryForCode(data)
});
}
};
Filter.prototype.handleDisplay = function(code, callback) {
code = parseInt(code, 10);
if (code === -1) {
callback('<br/>');
}
if (code === 0) {
if (this.stack.length) {
callback(this.resetStyles());
}
}
if (code === 1) {
callback(this.pushTag('b'));
}
if (code === 2) {
}
if ((2 < code && code < 5)) {
callback(this.pushTag('u'));
}
if ((4 < code && code < 7)) {
callback(this.pushTag('blink'));
}
if (code === 7) {
}
if (code === 8) {
callback(this.pushStyle('display:none'));
}
if (code === 9) {
callback(this.pushTag('strike'));
}
if (code === 24) {
callback(this.closeTag('u'));
}
if ((29 < code && code < 38)) {
callback(this.pushStyle("ef" + (code - 30)));
}
if (code === 39) {
callback(this.pushStyle("color:" + this.opts.fg));
}
if ((39 < code && code < 48)) {
callback(this.pushStyle("eb" + (code - 40)));
}
if (code === 49) {
callback(this.pushStyle("background-color:" + this.opts.bg));
}
if ((89 < code && code < 98)) {
callback(this.pushStyle("ef" + (8 + (code - 90))));
}
if ((99 < code && code < 108)) {
return callback(this.pushStyle("eb" + (8 + (code - 100))));
}
};
Filter.prototype.categoryForCode = function(code) {
code = parseInt(code, 10);
if (code === 0) {
return 'all';
} else if (code === 1) {
return 'bold';
} else if ((2 < code && code < 5)) {
return 'underline';
} else if ((4 < code && code < 7)) {
return 'blink';
} else if (code === 8) {
return 'hide';
} else if (code === 9) {
return 'strike';
} else if ((29 < code && code < 38) || code === 39 || (89 < code && code < 98)) {
return 'foreground-color';
} else if ((39 < code && code < 48) || code === 49 || (99 < code && code < 108)) {
return 'background-color';
} else {
return null;
}
};
Filter.prototype.pushTag = function(tag, style) {
if (style == null) {
style = '';
}
if (style.length && style.indexOf(':') === -1) {
style = STYLES[style];
}
this.stack.push(tag);
return ["<" + tag, (style ? " style=\"" + style + "\"" : void 0), ">"].join('');
};
Filter.prototype.pushText = function(text) {
if (this.opts.escapeXML) {
return entities.encodeXML(text);
} else {
return text;
}
};
Filter.prototype.pushStyle = function(style) {
return this.pushTag("span", style);
};
Filter.prototype.closeTag = function(style) {
var last;
if (this.stack.slice(-1)[0] === style) {
last = this.stack.pop();
}
if (last != null) {
return "</" + style + ">";
}
};
Filter.prototype.resetStyles = function() {
var stack, _ref;
_ref = [this.stack, []], stack = _ref[0], this.stack = _ref[1];
return stack.reverse().map(function(tag) {
return "</" + tag + ">";
}).join('');
};
Filter.prototype.tokenize = function(text, callback) {
var ansiHandler, ansiMatch, ansiMess, handler, i, length, newline, process, realText, remove, removeXterm256, tokens, _j, _len, _results1;
ansiMatch = false;
ansiHandler = 3;
remove = function(m) {
return '';
};
removeXterm256 = function(m, g1) {
callback('xterm256', g1);
return '';
};
newline = (function(_this) {
return function(m) {
if (_this.opts.newline) {
callback('display', -1);
} else {
callback('text', m);
}
return '';
};
})(this);
ansiMess = function(m, g1) {
var code, _j, _len;
ansiMatch = true;
if (g1.trim().length === 0) {
g1 = '0';
}
g1 = g1.trimRight(';').split(';');
for (_j = 0, _len = g1.length; _j < _len; _j++) {
code = g1[_j];
callback('display', code);
}
return '';
};
realText = function(m) {
callback('text', m);
return '';
};
tokens = [
{
pattern: /^\x08+/,
sub: remove
}, {
pattern: /^\x1b\[[012]?K/,
sub: remove
}, {
pattern: /^\x1b\[38;5;(\d+)m/,
sub: removeXterm256
}, {
pattern: /^\n+/,
sub: newline
}, {
pattern: /^\x1b\[((?:\d{1,3};?)+|)m/,
sub: ansiMess
}, {
pattern: /^\x1b\[?[\d;]{0,3}/,
sub: remove
}, {
pattern: /^([^\x1b\x08\n]+)/,
sub: realText
}
];
process = function(handler, i) {
var matches;
if (i > ansiHandler && ansiMatch) {
return;
} else {
ansiMatch = false;
}
matches = text.match(handler.pattern);
text = text.replace(handler.pattern, handler.sub);
if (matches == null) {
}
};
_results1 = [];
while ((length = text.length) > 0) {
for (i = _j = 0, _len = tokens.length; _j < _len; i = ++_j) {
handler = tokens[i];
process(handler, i);
}
if (text.length === length) {
break;
} else {
_results1.push(void 0);
}
}
return _results1;
};
return Filter;
})();

View file

@ -1,45 +0,0 @@
<main>
<article>
<section class="search">
<input type="search" spellcheck="false" placeholder="Filter commits for {{ repo.full_name}}" spellcheck="false" ng-model="search_text"/>
<menu>
<button type="button" ng-click="unwatch(repo)" ng-if="repo.starred && user">
<i class="material-icons">visibility</i>
</button>
<button type="button" ng-click="watch(repo)" ng-if="!repo.starred && user">
<i class="material-icons">visibility_off</i>
</button>
<a href="/{{ repo.full_name }}/edit" ng-if="repo.permissions.push">
<i class="material-icons">tune</i>
</a>
</menu>
</section>
<div class="blankslate" ng-show="!builds.length && !loading && !error">
<i class="material-icons">settings_ethernet</i>
<span>Push code to execute your first build.<br/>Be sure to configure you build using the <em>.drone.yml</em> file.</span>
</div>
<div class="alert alert-error" ng-show="!!error">
<i class="material-icons">error_outline</i>
<span>There was an error fetching the build history.</span>
</div>
<div>
<div class="list">
<a ng-repeat="build in builds | orderBy:'-number' | filter: search_text" ng-href="/{{ repo.full_name }}/{{ build.number}}">
<div class="column-status">
<div class="status {{ build.status }}">
<i class="material-icons">{{ build.status | icon }}</i>
</div>
</div>
<div class="column-fill">
<h2>{{ build.head_commit.message }}</h2>
<p><em>{{ build.head_commit.author.login }}</em> pushed to <em>{{ build.head_commit.branch }}</em> {{ build.started_at | fromNow }}</p>
</div>
</a>
</div>
</div>
</article>
</main>

View file

@ -1,8 +0,0 @@
<ol>
<li>
<a href="/">
<i class="material-icons">arrow_back</i>
</a>
</li>
<li>{{owner}} / {{name}}</li>
</ol>

View file

@ -1,59 +0,0 @@
<!-- <button ng-if="build.status !== 'pending' && build.status !== 'running'" ng-click="restart()">Restart</button>
<button ng-if="build.status === 'pending' || build.status === 'running'" ng-click="cancel()">Cancel</button>
-->
<main class="flex">
<aside>
<div>
<div class="build-section">
<div class="status"><i class="material-icons">close</i></div><!--remove-->
<div class="build-summary">
<h2><small class="status {{build.status}}">{{ build.status }}</small>{{ build.head_commit.message }}</h2>
<p><em>{{ build.head_commit.author.login }}</em> pushed to <em>{{ build.head_commit.branch }}</em> {{ build.started_at | fromNow }}</p>
</div>
</div>
<div ng-if="repo && repo.permissions && repo.permissions.push">
<button ng-if="build.status !== 'pending' && build.status !== 'running'" ng-click="restart()" class="button button-restart">Restart</button>
<button ng-if="build.status === 'running' && build.jobs.length === 1" ng-click="cancel()" class="button button-cancel">Cancel</button>
</div>
<div ng-class="{'list':true, 'job-list':true, 'matrix-list': build.jobs && build.jobs.length > 1 }">
<a ng-repeat="job in build.jobs" ng-href="{{ repo.full_name }}/{{ build.number }}/{{ job.number }}" ng-class="{'active': job.number == step }">
<div>
<div class="status {{ job.status }} status-small">
<i class="material-icons">{{ job.status | icon }}</i>
</div>
</div>
<div>{{ job.number }}</div>
<div>
<div class="param" ng-repeat="(key, value) in job.environment">
{{ key.toUpperCase() }}={{ value }}
</div>
<div class="meta-group" ng-if="job.status !== 'pending' && job.status !== 'running'">
<div class="meta">finished {{ job.started_at | fromNow }}</div>
<div class="meta">duration of {{ job.finished_at - job.started_at | toDuration }}</div>
<div class="meta">with exit code {{ job.exit_code }}</div>
</div>
<div class="meta-group" ng-if="job.status === 'running'">
<div class="meta">started {{ job.started_at | fromNow }}</div>
</div>
<div class="meta-group" ng-if="job.status === 'pending'">
<div class="meta">pending execution</div>
</div>
</div>
</a>
</div>
</div>
</aside>
<article class="console">
<div class="button-tail" ng-if="build.status === 'running'" ng-click="tail()">
<i class="material-icons">expand_more</i>
</div>
<pre id="term"></pre>
</article>
</main>

View file

@ -1,10 +0,0 @@
<ol>
<li>
<a ng-href="/{{ full_name }}">
<i class="material-icons">arrow_back</i>
</a>
</li>
<li>{{owner}} / {{name}}</li>
<li><i class="material-icons">chevron_right</i></li>
<li>{{number}}</li>
</ol>

View file

@ -1,37 +0,0 @@
<article>
<section class="commit-section">
<div class="row build-row">
<div>
<div ng-class="[ 'build-num', task.status ]" ng-if="task"></div>
<div ng-class="[ 'build-num', build.status ]" ng-if="!task"></div>
</div>
<div>
<h3>{{ build.head_commit.message }}</h3>
<p><strong>{{ build.head_commit.author.login }}</strong> pushed to <strong>{{ build.head_commit.branch}}</strong> {{ build.started_at | fromNow }}</p>
</div>
</div>
<div class="row build-row sub-build-row" ng-if="build.jobs.length > 1">
<div>
</div>
<div>
<h3 style="margin-top:0px">
<div ng-repeat="(key, value) in task.environment">
{{ key.toUpperCase() }}={{ value }}
</div>
</h3>
</div>
</div>
</section>
<pre id="term" ng-if="task && task.status !== 'pending'"></pre>
<button class="fab" ng-if="build.status === 'running'" ng-click="tail()"></button>
</article>
<button ng-if="build.status !== 'pending' && build.status !== 'running'" ng-click="restart()">Restart</button>
<button ng-if="build.status === 'pending' || build.status === 'running'" ng-click="cancel()">Cancel</button>

View file

@ -1,22 +0,0 @@
<div class="breadcrumb" style="position:relative;top:0px;">
<a ng-href="/{{ repo.full_name }}/{{ build.sequence }}" class="icon icon-home">
<i class="material-icons md-18">home</i>
</a>
<a ng-href="/{{ repo.full_name }}">{{ repo.owner }} / {{ repo.name }}</a>
<span class="spacer">&nbsp;</span>
<a ng-href="/{{ repo.full_name }}/{{ build.number }}">Build # {{ build.number }}</a>
<span class="spacer"></span>
<a href="#">Job #{{ task.number }}</a>
</div>
<div class="menu">
<a ng-href="/{{ repo.full_name }}/edit" class="nav-item settings float-right" ng-if="repo.permissions.pull">
<i class="material-icons md-18">edit</i>
</a>
<button ng-click="watch(repo)" ng-if="!repo.starred && user" class="nav-item star float-right">
<i class="material-icons md-18">star_border</i>
</button>
<button ng-click="unwatch(repo)" ng-if="repo.starred && user" class="nav-item unstar float-right">
<i class="material-icons md-18">star</i>
</button>
</div>

View file

@ -1,75 +0,0 @@
<header>
<div class="toolbar" ui-view="toolbar"></div>
<ul ng-if="user">
<li>
<img ng-src="{{ user.avatar_url }}" />
</li>
<li ng-init="show=false">
<a href="#" ng-click="show=true">
<span>{{user.login}}</span>
<i class="material-icons">more_vert</i>
</a>
<div class="backdrop" ng-show="show" ng-click="show=false"></div>
<div class="dropdown" ng-show="show" ng-click="show=false">
<ul>
<li><a href="/profile">Profile</a></li>
<li><a href="/users">Users</a></li>
<li><a href="/logout">Logout</a></li>
</ul>
</div>
</li>
</ul>
<ul ng-if="!user">
<li>
<a href="/login" class="button button-login">Login</a>
</li>
</ul>
</header>
<div ui-view="content"></div>
<style>
header ul li a i {
cursor:pointer;
}
header li {
position:relative;
}
.backdrop {
position:fixed;
top:0px;
left:0px;
right:0px;
bottom:0px;
z-index:99;
cursor:default;
}
.dropdown {
box-shadow: 0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12);
position: absolute;
top: 60px;
right: 15px;
width: 200px;
background: #FFF;
z-index:999;
padding: 10px 0px;
}
.dropdown ul {
float:inherit;
margin:0px;
}
.dropdown li {
display:block;
}
.dropdown li > a {
padding: 15px 20px;
display: block;
line-height: inherit;
text-transform: none;
}
.dropdown li > a:hover {
background:#F5F7F9;
}
</style>

View file

@ -1,65 +0,0 @@
<style>
a.box {
background: #FFF;
width: 250px;
padding: 20px;
box-shadow: 0px 0px 0 rgba(255, 255, 255, 0.05);
-webkit-transition: all .5s;
-webkit-box-flex: 0;
display: block;
text-decoration: none;
border-radius: 3px;
}
a.box:hover {
box-shadow: 7px 7px 0 rgba(255, 255, 255, 0.05);
}
div.login-logo {
background: url(/static/images/logo.svg) no-repeat center center;
background-size: 72px;
height: 150px;
-webkit-transition: all .5s;
}
div.login {
text-align: center;
padding: 15px;
color: #424242;
font-size: 18px;
text-transform: uppercase;
border-radius: 3px;
-webkit-transition: all .5s;
background: #424242;
color: rgba(255, 255, 255, 0.7);
}
body > div {
width: 100%;
height: 100%;
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
justify-content: center;
}
</style>
<a href="/authorize" target="_self" class="box">
<div class="login-logo"></div>
<div ng-switch="error" class="alert alert-error" ng-if="error">
<div ng-switch-when="internal_error">Oops. There was an unexpected error. Please try again.</div>
<div ng-switch-when="user_not_found">There was an error authorizing your account.</div>
<div ng-switch-when="access_denied_org">Login is restricted to approved organization members only</div>
<div ng-switch-when="access_denied">Self-registration is disabled. Please contact the system admin to grant
access.
</div>
</div>
<div class="login">Login</div>
</a>

View file

@ -1,20 +0,0 @@
<main>
<article>
<section>
<div class="row">
<div>Login</div>
<div>{{ user.login }}</div>
</div>
<div class="row">
<div>Email</div>
<div>{{ user.email }}</div>
</div>
<div class="row">
<div>Token</div>
<div ng-if="!token" style="color: #1E88E5;text-decoration: underline;cursor: pointer;" ng-click="showToken()">Click to Display Token</div>
<div ng-if="token">{{ token }}</div>
</div>
</section>
</article>
</main>

View file

@ -1,8 +0,0 @@
<ol>
<li>
<a href="/">
<i class="material-icons">arrow_back</i>
</a>
</li>
<li>Profile</li>
</ol>

View file

@ -1,20 +0,0 @@
<article>
<section style="background:transparent !important;">
<p style="color:#9E9E9E;font-size:15px;line-height:22px;">Register your repository with Drone to enable automated testing. Note that Drone
will attempt to add a post-commit hook to your repository. This may require administrative access.</p>
</section>
<div class="alert alert-error" ng-if="error !== undefined">
There was an error adding your repository. Please ensure the
repository exists and you have admin privileges.
</div>
<section>
<div style="padding:30px;">
<input type="text" placeholder="octocat/Hello-World" ng-model="slug" style="font-size:14px;padding:10px 20px;width:400px;border: 1px solid #d9d9d9;outline:none;" />
<div style="margin-top:20px;">
<a href="/" style="display: inline-block;font-size:14px; padding:10px 20px;text-transform:uppercase;background:#EEE;text-decoration:none;color:#616161;">Cancel</a>
<button ng-click="add(slug)" style="display: inline-block;background:#EEE;font-size:14px; padding:10px 20px;text-transform:uppercase;cursor:pointer;color:#616161;">Add</button>
</div>
</div>
</section>
</article>

View file

@ -1,6 +0,0 @@
<div class="breadcrumb" style="position:relative;top:0px;">
<a href="/" class="icon icon-home">
<i class="material-icons md-18">home</i>
</a>
<a href="#">Add Repository</a>
</div>

View file

@ -1,19 +0,0 @@
<main>
<article>
<section style="padding:30px; background-color:#bf616a;">
<button ng-click="delete(repo.full_name)" style="color:rgba(255,255,255,0.9);
text-transform: uppercase;
text-decoration: none;
border: 1px solid rgba(255,255,255,0.9);
padding: 10px;
background:transparent;
display:inline-block;
cursor:pointer;
margin-right: 10px;">Delete</button>
<span style="color:rgba(255,255,255,0.9);font-size:16px;">Warning: this action cannot be undone.</span>
</section>
</article>
</main>

View file

@ -1,65 +0,0 @@
<main>
<article>
<section style="margin-top:20px;">
<div class="row">
<div>Post Commit Hooks</div>
<div>
<input id="post_commits" type="checkbox" hidden="hidden" ng-model="repo.hooks.push" ng-change="save(repo)"/>
<label for="post_commits" class="switch"></label>
</div>
</div>
<div class="row">
<div>Pull Request Hooks</div>
<div>
<input id="pull_requests" type="checkbox" hidden="hidden" ng-model="repo.hooks.pull_request"
ng-change="save(repo)"/>
<label for="pull_requests" class="switch"></label>
</div>
</div>
<div class="row" ng-if="user.admin">
<div>Trusted Code</div>
<div>
<input id="trusted" type="checkbox" hidden="hidden" ng-model="repo.trusted" ng-change="save(repo)"/>
<label for="trusted" class="switch"></label>
</div>
</div>
<div class="row" ng-if="user.admin">
<div>Timeout in minutes</div>
<div>
<input type="range" ng-model="repo.timeout" min="0" max="900" ng-blur="save(repo)"/>
<span class="slider-label">{{ repo.timeout }} minutes</span>
</div>
</div>
<div class="row">
<div>Badge</div>
<div>
<pre class="snippet">[![Build Status]({{ window.location.origin }}/api/badges/{{ repo.full_name }}/status.svg)]({{ window.location.origin }}/{{ repo.full_name }})</pre>
</div>
</div>
<div class="row">
<div>CCMenu</div>
<div>
<pre class="snippet">{{ window.location.origin }}/api/badges/{{ repo.full_name }}/cc.xml</pre>
</div>
</div>
<div class="row">
<div>Public Key</div>
<div>
<pre class="snippet">{{ repo.keypair.public }}</pre>
</div>
</div>
<a class="row" ng-href="{{ repo.full_name }}/secure">
<div>Secrets</div>
<div>
Inject secret variables into your build environment
</div>
</a>
<a class="row" ng-href="{{ repo.full_name }}/delete">
<div>Delete</div>
<div>Delete this repository and its build history</div>
</a>
</section>
</article>
</main>

View file

@ -1,23 +0,0 @@
<article>
<section style="padding:30px;">
<h2 style=" padding-left: 0px;
padding-top: 0px;
margin-bottom: 10px;">Private variables</h2>
<div ng-repeat="(key, value) in repo.params" class="row-env">
<div><span>export</span> {{ key }} <span>=</span></div>
<div>{{ value }}</div>
<button ng-click="deleteParam(key)" class="btn-remove"></button>
</div>
<form style="padding-top:30px;font-family:'Droid Sans Mono','Roboto','Arial';font-size:14px;">
<span style="color:#2196F3">export</span>
<input type="text" placeholder="FOO" ng-model="param.key" style="border:none;border-bottom:1px solid #9E9E9E;line-height:24px;font-family:'Droid Sans Mono','Roboto','Arial';font-size:14px;" />
<span style="color:#2196F3">=</span>
<input type="text" placeholder="BAR" ng-model="param.value" style="border:none;border-bottom:1px solid #9E9E9E;line-height:24px;font-family:'Droid Sans Mono','Roboto','Arial';font-size:14px;" />
<button ng-click="addParam(param)" style="background: #66bb6a;
padding: 8px 20px;
color: #FFF;
font-family: Roboto;
text-transform: uppercase;">add</button>
</form>
</section>
</article>

View file

@ -1,38 +0,0 @@
<main>
<article>
<section class="search">
<input type="search" spellcheck="false" placeholder="Create or find a repository (e.g. octocat/Hello-World)" ng-model="search_text" ng-keypress="add($event, search_text)"/>
</section>
<div class="blankslate" ng-show="!repos.length && !loading && !search_text && !error">
<i class="material-icons">control_point_duplicate</i>
<span>Get started by adding your repository.<br/>Just type the repository name in the text box above.</span>
</div>
<div class="alert alert-create-not-found" ng-show="!!search_text">
<i class="material-icons" ng-show="!waiting">control_point_duplicate</i>
<i class="material-icons waiting" ng-show="waiting">refresh</i>
<span ng-show="!waiting">Press &lt;enter&gt; to add <em>{{search_text}}</em></span>
<span ng-show="waiting">Configuring repository <em>{{search_text}}</em></span>
</div>
<div class="alert alert-error" ng-show="!!error">
<i class="material-icons">error_outline</i>
<span>There was an error adding your repository.<br/>Please ensure the
repository exists and you have admin privileges.</span>
</div>
<ul class="list cozy" ng-show="!waiting && !error">
<a class="row row-repo" ng-repeat="repo in repos | orderBy:'full_name' | filter: search_text" ng-href="/{{ repo.full_name }}">
<div class="column-avatar">
<img ng-src="{{ repo.avatar_url }}" />
</div>
<div class="column-fill">
<h2>{{ repo.name }}</h2>
</div>
</a>
</ul>
</article>
</main>

View file

@ -1 +0,0 @@
<a href="/" class="logo"></a>

View file

@ -1,28 +0,0 @@
<main>
<article>
<p style=" font-size: 16px;
margin-bottom: 10px;">Encrypt and store secret variables in the <code>.drone.sec</code> file<p>
<textarea spellcheck="false" placeholder="Enter a plaintext value here" ng-model="plaintext" style="
background-color: #eff1f5;
border-radius:3px;
border:none;
padding:10px;
min-height:100px;
max-width:100%;
display:block;
min-width:100%;
white-space: normal;
font-size: 14px;
word-wrap: normal;
word-break: break-all;"></textarea>
<button ng-click="encrypt(plaintext)">Encrypt</button>
<pre ng-if="secure" style=" white-space: normal;
font-size: 16px;
word-wrap: normal;
word-break: break-all;">{{secure}}</pre>
</article>
</main>

View file

@ -1,8 +0,0 @@
<ol>
<li>
<a ng-href="/{{ full_name}}">
<i class="material-icons">arrow_back</i>
</a>
</li>
<li>{{owner}} / <a ng-href="/{{ full_name}}">{{name}}</a></li>
</ol>

View file

@ -1,46 +0,0 @@
<main>
<article>
<section class="search">
<input type="search" spellcheck="false" placeholder="Create or find a user" ng-model="search_text" ng-keypress="add($event, search_text)" />
</section>
<div class="blankslate" ng-show="users.length === 1 && !search_text && !waiting">
<i class="material-icons">control_point_duplicate</i>
<span>Get started by adding your team members.<br/>Just type the user login (ie octocat) in the text box above.</span>
</div>
<div class="alert alert-create-not-found" ng-show="!!search_text">
<i class="material-icons" ng-show="!waiting">control_point_duplicate</i>
<i class="material-icons waiting" ng-show="!!waiting">sync</i>
<span>Press &lt;enter&gt; to add <em>{{search_text}}</em></span>
</div>
<div class="alert alert-success" ng-show="new_user">
<span>Successfully added user account <em>{{new_user.login}}</em>.</span>
</div>
<div class="alert alert-error" ng-show="!!error">
<i class="material-icons">error_outline</i>
<span>There was an error adding the user account.</span>
</div>
<ul class="list cozy user-list">
<li class="row row-user" ng-repeat="user in users | orderBy:'login' | filter: search_text">
<div class="column-avatar">
<img ng-src="{{ user.avatar_url || 'https://www.gravatar.com/avatar/00000000000000000000000000000000?d=mm&f=y' }}" />
</div>
<div class="column-fill">
<h2>{{ user.login }} <small ng-if="user.admin" class="label label-success">Admin</small></h2>
<menu>
<button ng-click="toggle(user)" ng-if="!user.admin" class="button success">Grant Admin</button>
<button ng-click="toggle(user)" ng-if="user.admin" class="button danger">Revoke Admin</button>
<button ng-click="remove(user)" class="button danger">Delete</button>
</menu>
</div>
</li>
</ul>
</article>
</main>

View file

@ -1,8 +0,0 @@
<ol>
<li>
<a href="/">
<i class="material-icons">arrow_back</i>
</a>
</li>
<li>Users</li>
</ol>

View file

@ -1,89 +0,0 @@
.alert {
padding: 40px;
background-color: #F5F7F9;
color: #4f5b66;
text-align: center;
font-size: 16px;
border-radius:3px;
margin-bottom:30px;
position:relative;
}
.alert em {
font-weight: bold;
color: #2b303b;
}
.alert i.material-icons {
font-size: 42px;
display: block;
text-align: center;
width: 100%;
margin-bottom: 20px;
}
.alert-error {
background: #bf616a;
color: #fff;
line-height:24px;
}
.alert-success {
background: #a3be8c;
color: #fff;
line-height:24px;
}
.alert-create-not-found {
color: rgba(255,255,255,0.95);
background:#59abe3;
background: #8fa1b3;
outline:none;
border:none;
width:100%;
font-family: Roboto;
}
.alert-success em,
.alert-error em,
.alert-create-not-found em {
font-weight: bold;
color: #fff;
font-size: 120%;
margin-left:10px;
}
@-webkit-keyframes delayed-rotate {
0% {
-webkit-transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
}
}
@-moz-keyframes delayed-rotate {
0% {
-moz-transform: rotate(0deg);
}
100% {
-moz-transform: rotate(360deg);
}
}
@keyframes delayed-rotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.waiting {
-webkit-animation-name: delayed-rotate;
-webkit-animation-duration: 1s;
-webkit-animation-iteration-count: infinite;
-webkit-animation-timing-function: ease-in-out;
-moz-animation-name: delayed-rotate;
-moz-animation-duration: 1s;
-moz-animation-iteration-count: infinite;
-moz-animation-timing-function: ease-in-out;
animation-name: delayed-rotate;
animation-duration: 1s;
animation-iteration-count: infinite;
animation-timing-function: ease-in-out;
}

View file

@ -1,23 +0,0 @@
.blankslate {
padding: 40px 20px;
background-color: #59abe3;
background-color: #8fa1b3;
color: #fff;
text-align: center;
font-size: 18px;
line-height:28px;
margin-bottom:30px;
}
.blankslate.clean-background {
}
.blankslate.spacious {
}
.blankslate i.material-icons {
font-size: 42px;
display: block;
text-align: center;
width: 100%;
margin-bottom: 20px;
}

View file

@ -1,3 +0,0 @@
@import url(//fonts.googleapis.com/css?family=Roboto:400,300,500,700);
@import url(//fonts.googleapis.com/css?family=Roboto+Mono:300,400,500);
@import url(//fonts.googleapis.com/icon?family=Material+Icons);

View file

@ -1,29 +0,0 @@
.label {
color: #FFF;
background:#8fa1b3;
font-size: 10px;
text-transform: uppercase;
padding: 4px 8px;
vertical-align: middle;
border-radius: 2px;
margin-left: 15px;
cursor:default;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.label-success {
color: #a3be8c;
border: 1px solid #a3be8c;
background: #fff;
/*
background: rgba(163, 190, 140, 0.25);
border: none;
font-size: 11px;*/
}
.label-failure {
color: #bf616a;
border: 1px solid #bf616a;
background: #fff;
}

View file

@ -1,99 +0,0 @@
.list > a,
.list > li {
display: flex;
padding: 30px 0px;
border-top: 1px solid #f0f4f7;
color:#4c555a;
text-decoration: none;
position: relative;
}
.list > a:first-child,
.list > li:first-child {
border-top: none;
}
.list .column-avatar {
width: 60px;
min-width: 60px;
}
.list .column-status {
width: 60px;
min-width: 60px;
}
.list .column-fill {
flex: 1 1 auto;
}
.comfortable > a,
.comfortable > div,
.comfortable > li {
padding-top:30px;
padding-bottom:30px;
}
.cozy > a,
.cozy > div,
.cozy > li {
padding-top:15px;
padding-bottom:15px;
}
.compact > a,
.compact > div,
.compact > li {
padding-top:10px;
padding-bottom:10px;
}
.list .column-avatar img {
width:32px;
height:32px;
border-radius:4px;
margin-left:5px;
}
.user-list .column-avatar img {
width:32px;
height:32px;
border-radius:50%;
margin-left:5px;
}
.list h2 {
line-height:32px;
font-size:18px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom:1px;
}
.list p {
color:#a7adba;
margin-top:3px;
font-size:14px;
line-height:20px;
}
.list em,
.list em {
color:#65737e;
}
/*
.matrix-list a.active {
background-color: #eff1f5;
margin-left: -10px;
padding-left: 10px;
}*/
.matrix-list a.active:after {
content:"chevron_right";
font-family: "Material Icons";
line-height:28px;
color: #c0c5ce;
font-weight: normal;
font-style: normal;
font-size: 24px;
letter-spacing: normal;
text-transform: none;
display: inline-block;
word-wrap: normal;
-webkit-font-feature-settings: 'liga';
-webkit-font-smoothing: antialiased;
margin-right:-5px;
}

View file

@ -1,769 +0,0 @@
html, body {
background:#fff;
color:#4c555a;
font-size:14px;
font-family:"Roboto";
min-height:100vh;
}
body > div {
display: flex;
flex-direction: column;
min-height: 100vh;
}
/*
* Header and Logo
*/
.logo {
float: left;
height: 36px;
width: 36px;
opacity: 1;
margin: 14px 0px 0px 14px;
margin: 14px 0px 0px 18px;
background: url(data:image/svg+xml;charset=utf-8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+DQo8IS0tIENyZWF0ZWQgd2l0aCBJbmtzY2FwZSAoaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvKSAtLT4NCg0KPHN2Zw0KICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIg0KICAgeG1sbnM6Y2M9Imh0dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL25zIyINCiAgIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyINCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciDQogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciDQogICB4bWxuczpzb2RpcG9kaT0iaHR0cDovL3NvZGlwb2RpLnNvdXJjZWZvcmdlLm5ldC9EVEQvc29kaXBvZGktMC5kdGQiDQogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSINCiAgIHdpZHRoPSIzMiINCiAgIGhlaWdodD0iMzIiDQogICBpZD0ic3ZnMiINCiAgIHZlcnNpb249IjEuMSINCiAgIGlua3NjYXBlOnZlcnNpb249IjAuNDguMy4xIHI5ODg2Ig0KICAgc29kaXBvZGk6ZG9jbmFtZT0iZHJvbmVfMzIucG5nIj4NCiAgPGRlZnMNCiAgICAgaWQ9ImRlZnM0IiAvPg0KICA8c29kaXBvZGk6bmFtZWR2aWV3DQogICAgIGlkPSJiYXNlIg0KICAgICBwYWdlY29sb3I9IiNmZmZmZmYiDQogICAgIGJvcmRlcmNvbG9yPSIjNjY2NjY2Ig0KICAgICBib3JkZXJvcGFjaXR5PSIxLjAiDQogICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwLjAiDQogICAgIGlua3NjYXBlOnBhZ2VzaGFkb3c9IjIiDQogICAgIGlua3NjYXBlOnpvb209IjcuOTE5NTk1OSINCiAgICAgaW5rc2NhcGU6Y3g9IjkuNjYyNzY2NCINCiAgICAgaW5rc2NhcGU6Y3k9IjYuMzk3Njg2NCINCiAgICAgaW5rc2NhcGU6ZG9jdW1lbnQtdW5pdHM9InB4Ig0KICAgICBpbmtzY2FwZTpjdXJyZW50LWxheWVyPSJsYXllcjEiDQogICAgIHNob3dncmlkPSJ0cnVlIg0KICAgICBpbmtzY2FwZTpzbmFwLWdsb2JhbD0iZmFsc2UiDQogICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTI5NSINCiAgICAgaW5rc2NhcGU6d2luZG93LWhlaWdodD0iNzQ0Ig0KICAgICBpbmtzY2FwZTp3aW5kb3cteD0iNjUiDQogICAgIGlua3NjYXBlOndpbmRvdy15PSIyNCINCiAgICAgaW5rc2NhcGU6d2luZG93LW1heGltaXplZD0iMSINCiAgICAgZml0LW1hcmdpbi10b3A9IjAiDQogICAgIGZpdC1tYXJnaW4tbGVmdD0iMCINCiAgICAgZml0LW1hcmdpbi1yaWdodD0iMCINCiAgICAgZml0LW1hcmdpbi1ib3R0b209IjAiPg0KICAgIDxpbmtzY2FwZTpncmlkDQogICAgICAgdHlwZT0ieHlncmlkIg0KICAgICAgIGlkPSJncmlkMjk5NiINCiAgICAgICBlbXBzcGFjaW5nPSI1Ig0KICAgICAgIHZpc2libGU9InRydWUiDQogICAgICAgZW5hYmxlZD0idHJ1ZSINCiAgICAgICBzbmFwdmlzaWJsZWdyaWRsaW5lc29ubHk9InRydWUiDQogICAgICAgb3JpZ2lueD0iLTIxLjcyMDc3OXB4Ig0KICAgICAgIG9yaWdpbnk9Ii05OTAuMzcxODhweCIgLz4NCiAgPC9zb2RpcG9kaTpuYW1lZHZpZXc+DQogIDxtZXRhZGF0YQ0KICAgICBpZD0ibWV0YWRhdGE3Ij4NCiAgICA8cmRmOlJERj4NCiAgICAgIDxjYzpXb3JrDQogICAgICAgICByZGY6YWJvdXQ9IiI+DQogICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0Pg0KICAgICAgICA8ZGM6dHlwZQ0KICAgICAgICAgICByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIiAvPg0KICAgICAgICA8ZGM6dGl0bGU+PC9kYzp0aXRsZT4NCiAgICAgIDwvY2M6V29yaz4NCiAgICA8L3JkZjpSREY+DQogIDwvbWV0YWRhdGE+DQogIDxnDQogICAgIGlua3NjYXBlOmxhYmVsPSJMYXllciAxIg0KICAgICBpbmtzY2FwZTpncm91cG1vZGU9ImxheWVyIg0KICAgICBpZD0ibGF5ZXIxIg0KICAgICB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjEuNzIwNzc5LC0yOS45OTAyODcpIj4NCiAgICA8cGF0aA0KICAgICAgIHNvZGlwb2RpOnR5cGU9ImFyYyINCiAgICAgICBzdHlsZT0iZmlsbDojMjQyNzI5O2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpldmVub2RkO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDowO3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLW9wYWNpdHk6MTtzdHJva2UtZGFzaGFycmF5Om5vbmUiDQogICAgICAgaWQ9InBhdGgyOTk4Ig0KICAgICAgIHNvZGlwb2RpOmN4PSIxNzIuMTA0NzQiDQogICAgICAgc29kaXBvZGk6Y3k9IjQ1OC4zOTI0OSINCiAgICAgICBzb2RpcG9kaTpyeD0iNS40Mjk1Njk3Ig0KICAgICAgIHNvZGlwb2RpOnJ5PSI1LjA1MDc2MjciDQogICAgICAgZD0ibSAxNzcuNTM0MzEsNDU4LjM5MjQ5IGEgNS40Mjk1Njk3LDUuMDUwNzYyNyAwIDEgMSAtMTAuODU5MTQsMCA1LjQyOTU2OTcsNS4wNTA3NjI3IDAgMSAxIDEwLjg1OTE0LDAgeiINCiAgICAgICB0cmFuc2Zvcm09Im1hdHJpeCgwLjc1NDE4MzA2LDAsMCwwLjgxMDc0NjgxLC05Mi4wNzA0MDEsLTMyMy4wMDQpIiAvPg0KICAgIDxwYXRoDQogICAgICAgc3R5bGU9ImZpbGw6IzI0MjcyOTtmaWxsLW9wYWNpdHk6MTtzdHJva2Utd2lkdGg6MDtzdHJva2UtbWl0ZXJsaW1pdDo0Ig0KICAgICAgIGQ9Im0gMzcuNzI4MDc1LDMyLjkyNjc2MiBjIDcuMTQ4NjU3LDAuMDU1OTkgMTUuMjc2MDY3LDUuMDk1MzgzIDE2LjAwNzI5NSwxNC41OTI2OTggbCAtOS42Nzg4MywwIGMgMCwwIC0xLjI0Njg3MSwtNS4yNDY5MTYgLTYuMzI4NDY1LC01LjIxMTY3OCAtNS4wODE1OTUsMC4wMzUyMiAtNi4zMjg0NjYsNS4yMTE2NzggLTYuMzI4NDY2LDUuMjExNjc4IGwgLTkuNjc4ODMsMCBjIDAuNDcwMjUsLTkuMzI5NDQ3IDguNDYyMDk3LC0xNC42NTE3NzYgMTYuMDA3Mjk2LC0xNC41OTI2OTggeiINCiAgICAgICBpZD0icmVjdDM4MTAiDQogICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCINCiAgICAgICBzb2RpcG9kaTpub2RldHlwZXM9InNjY3pjY3MiIC8+DQogICAgPHBhdGgNCiAgICAgICBzdHlsZT0iZmlsbDojMjQyNzI5O2ZpbGwtb3BhY2l0eToxO3N0cm9rZS13aWR0aDowO3N0cm9rZS1taXRlcmxpbWl0OjQiDQogICAgICAgZD0iTSAzNy43OTQ1NTMsNTkuOTkwMjYgQyAzMi40NjQyMDIsNjAuMDA0NDQgMjcuNDg0NjczLDU1Ljk4MjIyMSAyNS40NDM0MDYsNDkuNzUzMDM2IGwgNS45NTYyMDMsMCBjIDAsMCAxLjI4NDg2NSw1LjE4NzcxOSA2LjM2NjQ1OSw1LjE1MjQ4IDUuMDgxNTk0LC0wLjAzNTIyIDYuMjkwNDcyLC01LjE1MjQ4IDYuMjkwNDcyLC01LjE1MjQ4IGwgNS45NTYyMDMsMCBjIC0xLjMyNzc1LDYuNTg5Nzc0IC02Ljg4NzgzOCwxMC4yMjMwMzggLTEyLjIxODE5LDEwLjIzNzIyNCB6Ig0KICAgICAgIGlkPSJyZWN0MzgxMC0xIg0KICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiDQogICAgICAgc29kaXBvZGk6bm9kZXR5cGVzPSJzY2N6Y2NzIiAvPg0KICA8L2c+DQo8L3N2Zz4=) no-repeat center center;
}
header {
height:60px;
min-height:60px;
max-height:60px;
/*border-bottom:1px solid #f2f2f2;*/
box-sizing: border-box;
}
header ul {
float:right;
margin-right:20px;
}
header ul li span {
margin-right:7px;
margin-right:3px;
}
header li {
display:inline-block;
}
header li a {
line-height:60px;
display:inline-block;
vertical-align: middle;
padding:0px 10px;
text-transform: lowercase;
color:#4c555a;
text-decoration: none;
font-size:15px;
}
header li i {
vertical-align: middle;
}
header li.active a {
color:#0099e5;
}
header li img {
border-radius:50%;
width:32px;
height:32px;
vertical-align: middle;
margin-right:3px;
}
header ol {
float:left;
margin-left:15px;
}
header ol li {
line-height:60px;
display:inline-block;
vertical-align: middle;
font-size:21px;
}
header ol li:first-child {
font-size:0px;
}
header ol li i {
vertical-align: middle;
font-size:28px;
}
header ol li a {
line-height:inherit;
vertical-align: middle;
font-size:0px;
}
header ol li a i.material-icons {
vertical-align: middle;
font-size:25px;
}
header .private {
color: #ebcb8b;
opacity: 0.8;
/*color: #000;
opacity:0.25;*/
padding-left:15px;
margin-top:-5px;
display:none;
}
header .private i {
font-size:27px;
}
input[type="search"] {
font-family:Roboto;
}
/*
* Main Seciton / Layout
*/
.flex {
display:flex;
flex:1 1 auto;
margin-top:0px;
}
main article {
margin:0px auto;
max-width:900px;
padding:30px 40px;
}
.flex article {
padding-bottom:0px;
}
.flex article pre {
min-height: calc(100vh - 120px);
margin-bottom:20px;
}
main aside {
min-width:450px;
width:450px;
box-sizing: border-box;
padding:40px 50px;
}
main aside > div {
position: sticky;
top:0px;
}
/*
* Build Console
*/
.console {
margin:0px;
flex: 1 1 auto;
/*margin-top: -1px;*/
max-width:auto;
padding:30px 40px 20px 0px;
}
.console pre {
color:#eff1f5;
border-radius:2px;
background:#2b303b;
white-space: pre-wrap;
word-wrap: break-word;
box-sizing: border-box;
padding:35px 40px;
line-height:18px;
font-family: "Roboto Mono";
font-size:12px;
font-weight:300;
}
@media (-webkit-max-device-pixel-ratio: 1) {
.console pre {
font-weight:400;
line-height:20px;
font-size:13px;
}
}
/*
* Repo List
*/
/*
* Build List
*/
.repo-list { }
.repo-list li {
display:flex;
padding:30px 0px;
border-top:1px solid #f0f4f7;
position:relative;
}
.repo-list img {
width:32px;
height:32px;
border-radius:50%;
margin-left:5px;
}
.repo-list li:first-child {
border-top:none;
}
.repo-list li > div:first-child {
width:60px;
min-width:60px;
}
.repo-list li > div:nth-child(2) {
flex:1 1 auto;
}
.repo-list h2 {
line-height:32px;
font-size:18px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom:1px;
}
.repo-list p {
color:#a7adba;
margin-top:3px;
font-size:14px;
line-height:20px;
display:none;
}
/*
* Build List
*/
.build-list { }
.build-list li {
display:flex;
/*padding:30px 0px 30px 35px;*/
padding:30px 0px;
border-top:1px solid #f0f4f7;
position:relative;
}
/*.build-list li:nth-child(even) {
background:#FAFBFC;
}*/
.build-list li:first-child {
border-top:none;
}
.build-list li > div:first-child {
width:60px;
min-width:60px;
}
.build-list li > div:nth-child(2) {
flex:1 1 auto;
}
.build-list h2 {
line-height:32px;
font-size:18px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom:1px;
}
.build-section p,
.build-list p {
color:#a7adba;
margin-top:3px;
font-size:14px;
line-height:20px;
}
.build-section em,
.build-list em {
color:#65737e;
}
.build-list h2 small:before {
content:"#";
margin-right:3px;
}
.build-list h2 small:after {
content:" ";
margin-right:10px;
}
/*
* Build Details
*/
.build-section {
margin-bottom:40px;
display:flex;
}
.build-section > div:first-child {
min-width:60px;
width:60px;
display:none;
}
.build-section > div:last-child {
flex: 1 1 auto;
padding-top:4px;
font-size:17px;
line-height:22px;
}
.build-detail {
padding-left:50px;
box-sizing: border-box;
}
.build-summary h2 {
line-height:23px;
}
.build-summary h2 small {
font-size: 11px;
text-transform: uppercase;
padding: 3px 10px;
border-radius: 2px;
margin-right: 5px;
vertical-align: top;
color:#FFF;
width:auto;
height:auto;
display:inline;
}
.build-summary p {
margin-top:10px;
}
/*
* Job List
*/
.job-list { }
.job-list > a,
.job-list > li {
display:flex;
padding:20px 0px;
border-top:1px solid #f0f4f7;
}
.job-list > a > div:first-child,
.job-list > li > div:first-child {
width:50px;
min-width:50px;
}
.job-list > a > div:nth-child(2),
.job-list > li > div:nth-child(2) { /** TEMPORARILY HIDDEN. MAY DECIDE TO REMOVE */
min-width:25px;
font-size:13px;
text-align:right;
padding: 10px 20px 0px 10px;
font-weight:bold;
font-size:15px;
display:none;
}
.job-list > a > div:nth-child(3),
.job-list > li > div:nth-child(3) {
flex:1 1 auto;
font-size:13px;
}
.job-list > a div.param,
.job-list > li div.param {
margin-top:5px;
}
.job-list > a div.param:first-child,
.job-list > li div.param:first-child {
margin-top:9px;
}
.job-list > a div.meta,
.job-list > li div.meta {
margin-top: 5px;
color: #a7adba;
}
.job-list > a div.meta-group {
padding-top:10px;
display:none;
}
.job-list > a.active div.meta-group {
display:block;
}
.job-list:not(.matrix-list) > a div.meta:first-child {
color: #4c555a;
margin-top: -2px;
padding-bottom: 5px;
font-size: 14px;
}
li menu {
position:absolute;
top:15px;
right:0px;
display:none;
}
li:hover menu {
display:inline-block;
}
li menu .button {
border-radius:50%;
width:36px;
height:36px;
line-height:36px;
padding:0px;
color:#dfe1e8;
background: #fff;
border:1px solid #FFF;
outline:none;
cursor:pointer;
width: auto;
text-transform: uppercase;
padding: 0px 10px;
border-radius: 2px;
font-size: 11px;
line-height: 30px;
height: auto;
margin-left: 10px;
}
li menu .button:hover {
border:1px solid #a7adba;
background:#FFF;
color:#a7adba;
}
li menu .button i {
font-size:22px;
}
li menu .button.success {
color: #FFF;
border:1px solid #a3be8c;
background: #a3be8c;
color: #a3be8c;
background:#FFF;
}
li menu .button.danger {
color: #FFF;
background: #bf616a;
color:#bf616a;
background:#FFF;
border:1px solid #bf616a;
}
/*
* Tail button to follow a build
*/
.button-tail {
position: fixed;
bottom: 50px;
right: 80px;
width: 38px;
height: 38px;
background: rgba(255,255,255,0.2);
border-radius: 50%;
box-shadow: 1px 2px 2px rgba(0,0,0,0.2);
cursor: pointer;
bottom: 15px;
right: 60px;
}
.button-tail i {
text-align:center;
color:rgba(255,255,255,0.5);
width:38px;
line-height:38px;
display:inline-block;
}
/*
* Random buttons used throughtout
*/
/*
menu {
display:block;
text-align:right;
margin-bottom:20px;
}
menu .button {
margin-left:5px;
border:1px solid #f0f0f0;
}
menu .button span {
margin:0px 10px;
text-transform: uppercase;
}*/
.button {
border-radius:2px;
line-height:32px;
display:inline-block;
vertical-align: middle;
padding:4px 15px 4px 15px;
text-decoration: none;
font-size:13px;
background:#eff1f5;
color: rgba(0,0,0,0.5);
}
.button:hover {
background:#dfe1e8;
}
/*
.button-watch {
background: #a7adba;
}
.button-settings {
background:#d08770;
}
*/
.button i {
vertical-align: middle;
/*margin-right:5px;*/
line-height:17px;
font-size:18px;
}
.button-restart {
background: #FFF;
border: 1px solid #8fa1b3;
color: #8fa1b3;
font-size: 13px;
text-transform: uppercase;
margin-bottom: 20px;
margin-top:-20px;
padding: 0px 10px;
line-height: 25px;
cursor:pointer;
}
.button-cancel {
background: #FFF;
border: 1px solid #d08770;
color: #d08770;
font-size: 13px;
text-transform: uppercase;
margin-bottom: 20px;
margin-top:-20px;
padding: 0px 10px;
line-height: 25px;
cursor:pointer;
}
.button-cancel:hover {
background: #d08770;
border: 1px solid #d08770;
color: #fff;
}
.button-login {
margin-top: 15px;
font-size: 13px;
text-transform: uppercase;
background: #FFF;
border: 1px solid #8fa1b3;
color: #8fa1b3;
line-height: 25px;
}
.button-login:hover {
background: #8fa1b3;
border: 1px solid #8fa1b3;
color: #fff;
}
/*
* Status Indicator
*/
.status {
width: 32px;
height: 32px;
border-radius: 50%;
display: inline-block;
}
.status i {
line-height: 32px;
width: 32px;
vertical-align: middle;
text-align: center;
font-size:24px;
color:rgba(255,255,255,0.7);
}
.status-small {
width: 28px;
height: 28px;
}
.status-small i {
line-height: 28px;
width: 28px;
height: 28px;
}
/* Search Form */
.search {
display:block;
width:100%;
margin:0px 0px 37px 0px;
}
input[type="search"] {
background: #eff1f5;
line-height: 50px;
border:none;
border-radius:3px;
display:block;
width:100%;
font-size:14px;
padding:0px 15px;
font-family:"Roboto";
}
/* SETTINGS SECTION */
section .row {
display: flex;
position: relative;
text-decoration: none;
}
section .row a {
text-decoration: none;
}
section .row > div:first-child {
padding: 30px 0px;
border-bottom: 1px solid #f0f4f7;
width: 200px;
min-width: 200px;
font-size:15px;
color: #343d46;
}
section .row > div:last-child {
flex: 1 1 auto;
padding: 30px;
border-bottom: 1px solid #f0f4f7;
overflow:hidden;
color: #65737e;
}
section .row:last-child > div {
border-bottom: none;
}
pre.snippet-padding {
padding: 30px 0px;
}
pre.snippet {
white-space: pre-wrap;
word-wrap: break-word;
font-size: 13px;
line-height: 18px;
font-family: "Roboto Mono";
}
.slider-label {
display: inline-block;
margin-left: 10px;
}
::-webkit-input-placeholder { /* WebKit browsers */
color: #a7adba;
}
:-moz-placeholder { /* Mozilla Firefox 4 to 18 */
color: #a7adba;
opacity: 1;
}
::-moz-placeholder { /* Mozilla Firefox 19+ */
color: #a7adba;
opacity: 1;
}
:-ms-input-placeholder { /* Internet Explorer 10+ */
color: #a7adba;
}
/* ROUND INDICATORS */
.status {
border-radius:50%;
}
.status.error,
.status.killed,
.status.failure {
background: #bf616a;
}
.status.success {
background:#a3be8c;
}
.status.running,
.status.pending {
background:#ebcb8b;
}
.status.running i,
.status.pending i {
-webkit-animation-name: delayed-rotate;
-webkit-animation-duration: 1s;
-webkit-animation-iteration-count: infinite;
-webkit-animation-timing-function: ease-in-out;
-moz-animation-name: delayed-rotate;
-moz-animation-duration: 1s;
-moz-animation-iteration-count: infinite;
-moz-animation-timing-function: ease-in-out;
animation-name: delayed-rotate;
animation-duration: 1s;
animation-iteration-count: infinite;
animation-timing-function: ease-in-out;
}
.build-list li > div:first-child,
.job-list li > div:first-child {
width: 55px;
min-width: 55px;
}
/*
* Search Menu
*/
section.search {
background: #eff1f5;
margin: 0px 0px 37px 0px;
border-radius: 3px;
display:flex;
flex-shrink: 0;
}
section.search input[type="search"] {
line-height: 50px;
border: none;
border-radius: 3px;
display: block;
width: 100%;
font-size: 16px;
padding: 0px 15px;
flex:1 1 auto;
font-family:"Roboto";
}
section.search menu {
display:flex;
flex-shrink: 0;
}
section.search menu button,
section.search menu a {
line-height: 50px;
vertical-align: middle;
display: inline-block;
padding: 0px 15px;
color: rgba(0,0,0,0.3);
background:transparent;
border:none;
border-left: 1px solid #FFF;
cursor:pointer;
outline:none;
}
section.search menu i {
vertical-align: middle;
}
/*
* Lists
*/

View file

@ -1,109 +0,0 @@
input[type="range"]:focus ~ .slider-label {
display: inline-block;
}
input[type=range] {
-webkit-appearance: none;
margin: 6px 0;
width: 200px;
}
input[type=range]:focus {
outline: none;
}
input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 8px;
cursor: pointer;
animate: 0.2s;
box-shadow: none;
background: rgba(0, 150, 136, 0.5);
background: rgba(102, 187, 106, 0.5);
border-radius: 5px;
border: none;
}
input[type=range]::-webkit-slider-thumb {
box-shadow: none;
border: none;
height: 26px;
width: 26px;
border-radius: 50px;
background: #009688;
background: #66bb6a;
cursor: pointer;
-webkit-appearance: none;
margin-top: -10px;
}
input[type=range]:focus::-webkit-slider-runnable-track {
background: rgba(0, 150, 136, 0.5);
background: rgba(102, 187, 106, 0.5);
}
input[type=range]::-moz-range-track {
width: 100%;
height: 8px;
cursor: pointer;
animate: 0.2s;
box-shadow: none;
background: rgba(0, 150, 136, 0.5);
background: rgba(102, 187, 106, 0.5);
border-radius: 5px;
border: none;
}
input[type=range]::-moz-range-thumb {
box-shadow: none;
border: none;
height: 26px;
width: 26px;
border-radius: 50px;
background: #009688;
background: #66bb6a;
cursor: pointer;
}
input[type=range]::-ms-track {
width: 100%;
height: 8.4px;
cursor: pointer;
animate: 0.2s;
background: transparent;
border-color: transparent;
border-width: 16px 0;
color: transparent;
}
input[type=range]::-ms-fill-lower {
background: #2a6495;
border: 0.2px solid #010101;
border-radius: 2.6px;
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
}
input[type=range]::-ms-fill-upper {
background: #3071a9;
border: 0.2px solid #010101;
border-radius: 2.6px;
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
}
input[type=range]::-ms-thumb {
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
border: 1px solid #000000;
height: 36px;
width: 16px;
border-radius: 3px;
background: #ffffff;
cursor: pointer;
}
input[type=range]:focus::-ms-fill-lower {
background: #3071a9;
}
input[type=range]:focus::-ms-fill-upper {
background: #367ebd;
}

View file

@ -1,47 +0,0 @@
* {
box-sizing: border-box;
}
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
textarea, input { outline: none; }

View file

@ -1,52 +0,0 @@
/*
http://codepen.io/batazor/pen/KwKryj
*/
.switch {
display: inline-block;
position: relative;
width: 40px;
height: 8px;
border-radius: 10.416666666666668px;
background: #E0E0E0;
-webkit-transition: background 0.28s cubic-bezier(0.4, 0, 0.2, 1);
transition: background 0.28s cubic-bezier(0.4, 0, 0.2, 1);
vertical-align: middle;
cursor: pointer;
}
.switch::before {
content: '';
position: absolute;
top: -8.604166666666667px;
left: -2.604166666666667px;
width: 26.04166666666667px;
height: 26.04166666666667px;
background: #bdbdbd;
border-radius: 50%;
-webkit-transition: left 0.28s cubic-bezier(0.4, 0, 0.2, 1), background 0.28s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.28s cubic-bezier(0.4, 0, 0.2, 1);
transition: left 0.28s cubic-bezier(0.4, 0, 0.2, 1), background 0.28s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.28s cubic-bezier(0.4, 0, 0.2, 1);
}
.switch:active::before {
box-shadow: 0 2px 10.416666666666668px rgba(0, 0, 0, 0.28), 0 0 0 25px rgba(0, 0, 0, 0.1);
}
.switch:active::before {
box-shadow: 0 2px 10.416666666666668px rgba(0, 0, 0, 0.28), 0 0 0 25px rgba(0, 0, 0, 0.1);
}
input:checked + .switch {
background: rgba(0, 150, 136, 0.5);
background: rgba(102, 187, 106, 0.5);
}
input:checked + .switch::before {
left: 20.562499999999996px;
background: #009688;
background: #66bb6a;
}
input:checked + .switch:active::before {
box-shadow: 0 2px 10.416666666666668px rgba(0, 0, 0, 0.28), 0 0 0 25px rgba(0, 150, 136, 0.2);
}

View file

@ -1,19 +0,0 @@
package bus
const (
EventRepo = "repo"
EventUser = "user"
EventAgent = "agent"
)
type Event struct {
Kind string
Name string
Msg []byte
}
type Bus interface {
Subscribe(chan *Event)
Unsubscribe(chan *Event)
Send(*Event)
}

View file

@ -1,471 +0,0 @@
// Copyright 2011 The goauth2 Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package oauth supports making OAuth2-authenticated HTTP requests.
//
// Example usage:
//
// // Specify your configuration. (typically as a global variable)
// var config = &oauth.Config{
// ClientId: YOUR_CLIENT_ID,
// ClientSecret: YOUR_CLIENT_SECRET,
// Scope: "https://www.googleapis.com/auth/buzz",
// AuthURL: "https://accounts.google.com/o/oauth2/auth",
// TokenURL: "https://accounts.google.com/o/oauth2/token",
// RedirectURL: "http://you.example.org/handler",
// }
//
// // A landing page redirects to the OAuth provider to get the auth code.
// func landing(w http.ResponseWriter, r *http.Request) {
// http.Redirect(w, r, config.AuthCodeURL("foo"), http.StatusFound)
// }
//
// // The user will be redirected back to this handler, that takes the
// // "code" query parameter and Exchanges it for an access token.
// func handler(w http.ResponseWriter, r *http.Request) {
// t := &oauth.Transport{Config: config}
// t.Exchange(r.FormValue("code"))
// // The Transport now has a valid Token. Create an *http.Client
// // with which we can make authenticated API requests.
// c := t.Client()
// c.Post(...)
// // ...
// // btw, r.FormValue("state") == "foo"
// }
//
package oauth2
import (
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"mime"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"sync"
"time"
)
// OAuthError is the error type returned by many operations.
//
// In retrospect it should not exist. Don't depend on it.
type OAuthError struct {
prefix string
msg string
}
func (oe OAuthError) Error() string {
return "OAuthError: " + oe.prefix + ": " + oe.msg
}
// Cache specifies the methods that implement a Token cache.
type Cache interface {
Token() (*Token, error)
PutToken(*Token) error
}
// CacheFile implements Cache. Its value is the name of the file in which
// the Token is stored in JSON format.
type CacheFile string
func (f CacheFile) Token() (*Token, error) {
file, err := os.Open(string(f))
if err != nil {
return nil, OAuthError{"CacheFile.Token", err.Error()}
}
defer file.Close()
tok := &Token{}
if err := json.NewDecoder(file).Decode(tok); err != nil {
return nil, OAuthError{"CacheFile.Token", err.Error()}
}
return tok, nil
}
func (f CacheFile) PutToken(tok *Token) error {
file, err := os.OpenFile(string(f), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return OAuthError{"CacheFile.PutToken", err.Error()}
}
if err := json.NewEncoder(file).Encode(tok); err != nil {
file.Close()
return OAuthError{"CacheFile.PutToken", err.Error()}
}
if err := file.Close(); err != nil {
return OAuthError{"CacheFile.PutToken", err.Error()}
}
return nil
}
// Config is the configuration of an OAuth consumer.
type Config struct {
// ClientId is the OAuth client identifier used when communicating with
// the configured OAuth provider.
ClientId string
// ClientSecret is the OAuth client secret used when communicating with
// the configured OAuth provider.
ClientSecret string
// Scope identifies the level of access being requested. Multiple scope
// values should be provided as a space-delimited string.
Scope string
// AuthURL is the URL the user will be directed to in order to grant
// access.
AuthURL string
// TokenURL is the URL used to retrieve OAuth tokens.
TokenURL string
// RedirectURL is the URL to which the user will be returned after
// granting (or denying) access.
RedirectURL string
// TokenCache allows tokens to be cached for subsequent requests.
TokenCache Cache
// AccessType is an OAuth extension that gets sent as the
// "access_type" field in the URL from AuthCodeURL.
// See https://developers.google.com/accounts/docs/OAuth2WebServer.
// It may be "online" (the default) or "offline".
// If your application needs to refresh access tokens when the
// user is not present at the browser, then use offline. This
// will result in your application obtaining a refresh token
// the first time your application exchanges an authorization
// code for a user.
AccessType string
// ApprovalPrompt indicates whether the user should be
// re-prompted for consent. If set to "auto" (default) the
// user will be prompted only if they haven't previously
// granted consent and the code can only be exchanged for an
// access token.
// If set to "force" the user will always be prompted, and the
// code can be exchanged for a refresh token.
ApprovalPrompt string
}
// Token contains an end-user's tokens.
// This is the data you must store to persist authentication.
type Token struct {
AccessToken string
RefreshToken string
Expiry time.Time // If zero the token has no (known) expiry time.
// Extra optionally contains extra metadata from the server
// when updating a token. The only current key that may be
// populated is "id_token". It may be nil and will be
// initialized as needed.
Extra map[string]string
}
// Expired reports whether the token has expired or is invalid.
func (t *Token) Expired() bool {
if t.AccessToken == "" {
return true
}
if t.Expiry.IsZero() {
return false
}
return t.Expiry.Before(time.Now())
}
// Transport implements http.RoundTripper. When configured with a valid
// Config and Token it can be used to make authenticated HTTP requests.
//
// t := &oauth.Transport{config}
// t.Exchange(code)
// // t now contains a valid Token
// r, _, err := t.Client().Get("http://example.org/url/requiring/auth")
//
// It will automatically refresh the Token if it can,
// updating the supplied Token in place.
type Transport struct {
*Config
*Token
// mu guards modifying the token.
mu sync.Mutex
// Transport is the HTTP transport to use when making requests.
// It will default to http.DefaultTransport if nil.
// (It should never be an oauth.Transport.)
Transport http.RoundTripper
}
// Client returns an *http.Client that makes OAuth-authenticated requests.
func (t *Transport) Client() *http.Client {
return &http.Client{Transport: t}
}
func (t *Transport) transport() http.RoundTripper {
if t.Transport != nil {
return t.Transport
}
return http.DefaultTransport
}
// AuthCodeURL returns a URL that the end-user should be redirected to,
// so that they may obtain an authorization code.
func (c *Config) AuthCodeURL(state string) string {
url_, err := url.Parse(c.AuthURL)
if err != nil {
panic("AuthURL malformed: " + err.Error())
}
q := url.Values{
"response_type": {"code"},
"client_id": {c.ClientId},
"state": condVal(state),
"scope": condVal(c.Scope),
"redirect_uri": condVal(c.RedirectURL),
"access_type": condVal(c.AccessType),
"approval_prompt": condVal(c.ApprovalPrompt),
}.Encode()
if url_.RawQuery == "" {
url_.RawQuery = q
} else {
url_.RawQuery += "&" + q
}
return url_.String()
}
func condVal(v string) []string {
if v == "" {
return nil
}
return []string{v}
}
// Exchange takes a code and gets access Token from the remote server.
func (t *Transport) Exchange(code string) (*Token, error) {
if t.Config == nil {
return nil, OAuthError{"Exchange", "no Config supplied"}
}
// If the transport or the cache already has a token, it is
// passed to `updateToken` to preserve existing refresh token.
tok := t.Token
if tok == nil && t.TokenCache != nil {
tok, _ = t.TokenCache.Token()
}
if tok == nil {
tok = new(Token)
}
err := t.updateToken(tok, url.Values{
"grant_type": {"authorization_code"},
"redirect_uri": {t.RedirectURL},
"scope": {t.Scope},
"code": {code},
})
if err != nil {
return nil, err
}
t.Token = tok
if t.TokenCache != nil {
return tok, t.TokenCache.PutToken(tok)
}
return tok, nil
}
// RoundTrip executes a single HTTP transaction using the Transport's
// Token as authorization headers.
//
// This method will attempt to renew the Token if it has expired and may return
// an error related to that Token renewal before attempting the client request.
// If the Token cannot be renewed a non-nil os.Error value will be returned.
// If the Token is invalid callers should expect HTTP-level errors,
// as indicated by the Response's StatusCode.
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
accessToken, err := t.getAccessToken()
if err != nil {
return nil, err
}
// To set the Authorization header, we must make a copy of the Request
// so that we don't modify the Request we were given.
// This is required by the specification of http.RoundTripper.
req = cloneRequest(req)
req.Header.Set("Authorization", "Bearer "+accessToken)
// Make the HTTP request.
return t.transport().RoundTrip(req)
}
func (t *Transport) getAccessToken() (string, error) {
t.mu.Lock()
defer t.mu.Unlock()
if t.Token == nil {
if t.Config == nil {
return "", OAuthError{"RoundTrip", "no Config supplied"}
}
if t.TokenCache == nil {
return "", OAuthError{"RoundTrip", "no Token supplied"}
}
var err error
t.Token, err = t.TokenCache.Token()
if err != nil {
return "", err
}
}
// Refresh the Token if it has expired.
if t.Expired() {
if err := t.Refresh(); err != nil {
return "", err
}
}
if t.AccessToken == "" {
return "", errors.New("no access token obtained from refresh")
}
return t.AccessToken, nil
}
// cloneRequest returns a clone of the provided *http.Request.
// The clone is a shallow copy of the struct and its Header map.
func cloneRequest(r *http.Request) *http.Request {
// shallow copy of the struct
r2 := new(http.Request)
*r2 = *r
// deep copy of the Header
r2.Header = make(http.Header)
for k, s := range r.Header {
r2.Header[k] = s
}
return r2
}
// Refresh renews the Transport's AccessToken using its RefreshToken.
func (t *Transport) Refresh() error {
if t.Token == nil {
return OAuthError{"Refresh", "no existing Token"}
}
if t.RefreshToken == "" {
return OAuthError{"Refresh", "Token expired; no Refresh Token"}
}
if t.Config == nil {
return OAuthError{"Refresh", "no Config supplied"}
}
err := t.updateToken(t.Token, url.Values{
"grant_type": {"refresh_token"},
"refresh_token": {t.RefreshToken},
})
if err != nil {
return err
}
if t.TokenCache != nil {
return t.TokenCache.PutToken(t.Token)
}
return nil
}
// AuthenticateClient gets an access Token using the client_credentials grant
// type.
func (t *Transport) AuthenticateClient() error {
if t.Config == nil {
return OAuthError{"Exchange", "no Config supplied"}
}
if t.Token == nil {
t.Token = &Token{}
}
return t.updateToken(t.Token, url.Values{"grant_type": {"client_credentials"}})
}
// providerAuthHeaderWorks reports whether the OAuth2 server identified by the tokenURL
// implements the OAuth2 spec correctly
// See https://code.google.com/p/goauth2/issues/detail?id=31 for background.
// In summary:
// - Reddit only accepts client secret in the Authorization header
// - Dropbox accepts either it in URL param or Auth header, but not both.
// - Google only accepts URL param (not spec compliant?), not Auth header
func providerAuthHeaderWorks(tokenURL string) bool {
if strings.HasPrefix(tokenURL, "https://accounts.google.com/") ||
strings.HasPrefix(tokenURL, "https://github.com/") ||
strings.HasPrefix(tokenURL, "https://api.instagram.com/") ||
strings.HasPrefix(tokenURL, "https://www.douban.com/") {
// Some sites fail to implement the OAuth2 spec fully.
return false
}
// Assume the provider implements the spec properly
// otherwise. We can add more exceptions as they're
// discovered. We will _not_ be adding configurable hooks
// to this package to let users select server bugs.
return true
}
// updateToken mutates both tok and v.
func (t *Transport) updateToken(tok *Token, v url.Values) error {
v.Set("client_id", t.ClientId)
v.Set("client_secret", t.ClientSecret)
client := &http.Client{Transport: t.transport()}
req, err := http.NewRequest("POST", t.TokenURL, strings.NewReader(v.Encode()))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.SetBasicAuth(t.ClientId, t.ClientSecret)
r, err := client.Do(req)
if err != nil {
return err
}
defer r.Body.Close()
if r.StatusCode != 200 {
return OAuthError{"updateToken", "Unexpected HTTP status " + r.Status}
}
var b struct {
Access string `json:"access_token"`
Refresh string `json:"refresh_token"`
ExpiresIn int64 `json:"expires_in"` // seconds
Id string `json:"id_token"`
}
body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1<<20))
if err != nil {
return err
}
content, _, _ := mime.ParseMediaType(r.Header.Get("Content-Type"))
switch content {
case "application/x-www-form-urlencoded", "text/plain":
vals, err := url.ParseQuery(string(body))
if err != nil {
return err
}
b.Access = vals.Get("access_token")
b.Refresh = vals.Get("refresh_token")
b.ExpiresIn, _ = strconv.ParseInt(vals.Get("expires_in"), 10, 64)
b.Id = vals.Get("id_token")
default:
if err = json.Unmarshal(body, &b); err != nil {
return fmt.Errorf("got bad response from server: %q", body)
}
}
if b.Access == "" {
return errors.New("received empty access token from authorization server")
}
tok.AccessToken = b.Access
// Don't overwrite `RefreshToken` with an empty value
if b.Refresh != "" {
tok.RefreshToken = b.Refresh
}
if b.ExpiresIn == 0 {
tok.Expiry = time.Time{}
} else {
tok.Expiry = time.Now().Add(time.Duration(b.ExpiresIn) * time.Second)
}
if b.Id != "" {
if tok.Extra == nil {
tok.Extra = make(map[string]string)
}
tok.Extra["id_token"] = b.Id
}
return nil
}

View file

@ -1,122 +0,0 @@
package plugin
import (
"bytes"
"encoding/json"
"io"
"net/http"
"net/url"
"github.com/drone/drone/pkg/queue"
)
type Client struct {
url string
token string
}
func New(url, token string) *Client {
return &Client{url, token}
}
// Publish makes an http request to the remote queue
// to insert work at the tail.
func (c *Client) Publish(work *queue.Work) error {
return c.send("POST", "/queue", work, nil)
}
// Remove makes an http request to the remote queue to
// remove the specified work item.
func (c *Client) Remove(work *queue.Work) error {
return c.send("DELETE", "/queue", work, nil)
}
// Pull makes an http request to the remote queue to
// retrieve work. This initiates a long poll and will
// block until complete.
func (c *Client) Pull() *queue.Work {
out := &queue.Work{}
err := c.send("POST", "/queue/pull", nil, out)
if err != nil {
// TODO handle error
}
return out
}
// Pull makes an http request to the remote queue to
// retrieve work. This initiates a long poll and will
// block until complete.
func (c *Client) PullClose(cn queue.CloseNotifier) *queue.Work {
out := &queue.Work{}
err := c.send("POST", "/queue/pull", nil, out)
if err != nil {
// TODO handle error
}
return out
}
// Ack makes an http request to the remote queue
// to acknowledge an item in the queue was processed.
func (c *Client) Ack(work *queue.Work) error {
return c.send("POST", "/queue/ack", nil, nil)
}
// Items makes an http request to the remote queue
// to fetch a list of all work.
func (c *Client) Items() []*queue.Work {
out := []*queue.Work{}
err := c.send("GET", "/queue/items", nil, &out)
if err != nil {
// TODO handle error
}
return out
}
// send is a helper function that makes an authenticated
// request to the remote http plugin.
func (c *Client) send(method, path string, in interface{}, out interface{}) error {
url_, err := url.Parse(c.url + path)
if err != nil {
return err
}
var buf io.ReadWriter
if in != nil {
buf = new(bytes.Buffer)
err := json.NewEncoder(buf).Encode(in)
if err != nil {
return err
}
}
req, err := http.NewRequest(method, url_.String(), buf)
if err != nil {
return err
}
req.Header.Add("Authorization", "Bearer "+c.token)
req.Header.Add("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if out == nil {
return nil
}
return json.NewDecoder(resp.Body).Decode(out)
}
// In order to implement PullClose() we'll need to use a custom transport:
//
// tr := &http.Transport{}
// client := &http.Client{Transport: tr}
// c := make(chan error, 1)
// go func() { c <- f(client.Do(req)) }()
// select {
// case <-ctx.Done():
// tr.CancelRequest(req)
// <-c // Wait for f to return.
// return ctx.Err()
// case err := <-c:
// return err
// }

View file

@ -1,111 +0,0 @@
package plugin
import (
"net/http"
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin"
"github.com/drone/drone/pkg/queue"
)
// Handle returns an http.Handler that enables a remote
// client to interop with a Queue over http.
func Handle(queue queue.Queue, token string) http.Handler {
r := gin.New()
// middleware to validate the authorization token
// and to inject the queue into the http context.
bearer := "Bearer " + token
r.Use(func(c *gin.Context) {
if c.Request.Header.Get("Authorization") != bearer {
c.AbortWithStatus(403)
return
}
c.Set("queue", queue)
c.Next()
})
r.POST("/queue", publish)
r.DELETE("/queue", remove)
r.POST("/queue/pull", pull)
r.POST("/queue/ack", ack)
r.POST("/queue/items", items)
return r
}
// publish handles an http request to the queue
// to insert work at the tail.
func publish(c *gin.Context) {
q := fromContext(c)
work := &queue.Work{}
if !c.Bind(work) {
c.AbortWithStatus(400)
return
}
err := q.Publish(work)
if err != nil {
c.Fail(500, err)
return
}
c.Writer.WriteHeader(200)
}
// remove handles an http request to the queue
// to remove a work item.
func remove(c *gin.Context) {
q := fromContext(c)
work := &queue.Work{}
if !c.Bind(work) {
c.AbortWithStatus(400)
return
}
err := q.Remove(work)
if err != nil {
c.Fail(500, err)
return
}
c.Writer.WriteHeader(200)
}
// pull handles an http request to the queue
// to retrieve work.
func pull(c *gin.Context) {
q := fromContext(c)
work := q.PullClose(c.Writer)
if work == nil {
c.AbortWithStatus(500)
return
}
c.JSON(200, work)
}
// ack handles an http request to the queue
// to confirm an item was successfully pulled.
func ack(c *gin.Context) {
q := fromContext(c)
work := &queue.Work{}
if !c.Bind(work) {
c.AbortWithStatus(400)
return
}
err := q.Ack(work)
if err != nil {
c.Fail(500, err)
return
}
c.Writer.WriteHeader(200)
}
// items handles an http request to the queue to
// return a list of all work items.
func items(c *gin.Context) {
q := fromContext(c)
items := q.Items()
c.JSON(200, items)
}
// helper function to retrieve the Queue from
// the context and cast appropriately.
func fromContext(c *gin.Context) queue.Queue {
return c.MustGet("queue").(queue.Queue)
}

View file

@ -1,34 +0,0 @@
package queue
type Queue interface {
// Publish inserts work at the tail of this queue, waiting for
// space to become available if the queue is full.
Publish(*Work) error
// Remove removes the specified work item from this queue,
// if it is present.
Remove(*Work) error
// Pull retrieves and removes the head of this queue, waiting
// if necessary until work becomes available.
Pull() *Work
// PullClose retrieves and removes the head of this queue,
// waiting if necessary until work becomes available. The
// CloseNotifier should be provided to clone the channel
// if the subscribing client terminates its connection.
PullClose(CloseNotifier) *Work
// Ack acknowledges an item in the queue was processed.
Ack(*Work) error
// Items returns a slice containing all of the work in this
// queue, in proper sequence.
Items() []*Work
}
type CloseNotifier interface {
// CloseNotify returns a channel that receives a single value
// when the client connection has gone away.
CloseNotify() <-chan bool
}

View file

@ -1,76 +0,0 @@
package queue
import (
"io"
common "github.com/drone/drone/pkg/types"
)
// Work represents an item for work to be
// processed by a worker.
type Work struct {
System *common.System `json:"system"`
User *common.User `json:"user"`
Repo *common.Repo `json:"repo"`
Build *common.Build `json:"build"`
BuildPrev *common.Build `json:"build_last"`
Keys *common.Keypair `json:"keypair"`
Netrc *common.Netrc `json:"netrc"`
Config []byte `json:"config"`
Secret []byte `json:"secret"`
}
// represents a worker that has connected
// to the system in order to perform work
type Worker struct {
Name string
Addr string
IsHealthy bool
}
// Ping pings to worker to verify it is
// available and in good health.
func (w *Worker) Ping() (bool, error) {
return false, nil
}
// Logs fetches the logs for a work item.
func (w *Worker) Logs() (io.Reader, error) {
return nil, nil
}
// Cancel cancels a work item.
func (w *Worker) Cancel() error {
return nil
}
// type Monitor struct {
// manager *Manager
// }
// func NewMonitor(manager *Manager) *Monitor {
// return &Monitor{manager}
// }
// // start is a helper function that is used to monitor
// // all registered workers and ensure they are in a
// // healthy state.
// func (m *Monitor) Start() {
// ticker := time.NewTicker(1 * time.Hour)
// go func() {
// for {
// select {
// case <-ticker.C:
// workers := m.manager.Workers()
// for _, worker := range workers {
// // ping the worker to make sure it is
// // available and still accepting builds.
// if _, err := worker.Ping(); err != nil {
// m.manager.SetHealth(worker, false)
// } else {
// m.manager.SetHealth(worker, true)
// }
// }
// }
// }
// }

View file

@ -1,74 +0,0 @@
package client
// import (
// "net"
// "net/http"
// "net/rpc"
// common "github.com/drone/drone/pkg/types"
// )
// // Client communicates with a Remote plugin using the
// // net/rpc protocol.
// type Client struct {
// *rpc.Client
// }
// // New returns a new, remote datastore backend that connects
// // via tcp and exchanges data using Go's RPC mechanism.
// func New(conf *config.Config) (*Client, error) {
// // conn, err := net.Dial("tcp", conf.Server.Addr)
// // if err != nil {
// // return nil, err
// // }
// // client := &Client{
// // rpc.NewClient(conn),
// // }
// // return client, nil
// return nil, nil
// }
// func (c *Client) Login(token, secret string) (*common.User, error) {
// return nil, nil
// }
// // Repo fetches the named repository from the remote system.
// func (c *Client) Repo(u *common.User, owner, repo string) (*common.Repo, error) {
// return nil, nil
// }
// func (c *Client) Perm(u *common.User, owner, repo string) (*common.Perm, error) {
// return nil, nil
// }
// func (c *Client) Script(u *common.User, r *common.Repo, b *common.Build) ([]byte, error) {
// return nil, nil
// }
// func (c *Client) Status(u *common.User, r *common.Repo, b *common.Build, link string) error {
// return nil
// }
// func (c *Client) Activate(u *common.User, r *common.Repo, k *common.Keypair, link string) error {
// return nil
// }
// func (c *Client) Deactivate(u *common.User, r *common.Repo, link string) error {
// return nil
// }
// func (c *Client) Hook(r *http.Request) (*common.Hook, error) {
// hook := new(common.Hook)
// header := make(http.Header)
// copyHeader(r.Header, header)
// return hook, nil
// }
// func copyHeader(dst, src http.Header) {
// for k, vv := range src {
// for _, v := range vv {
// dst.Add(k, v)
// }
// }
// }

View file

@ -1,22 +0,0 @@
package runner
import (
"io"
"github.com/drone/drone/pkg/queue"
"github.com/drone/drone/pkg/types"
)
type Runner interface {
Run(work *queue.Work) error
Cancel(*types.Job) error
Logs(*types.Job) (io.ReadCloser, error)
}
// Updater defines a set of functions that are required for
// the runner to sent Drone updates during a build.
type Updater interface {
SetBuild(*types.User, *types.Repo, *types.Build) error
SetJob(*types.Repo, *types.Build, *types.Job) error
SetLogs(*types.Repo, *types.Build, *types.Job, io.ReadCloser) error
}

View file

@ -1,36 +0,0 @@
package builtin
import (
"database/sql"
"github.com/drone/drone/pkg/types"
)
type Agentstore struct {
*sql.DB
}
func NewAgentstore(db *sql.DB) *Agentstore {
return &Agentstore{db}
}
// Agent returns an agent by ID.
func (db *Agentstore) Agent(build *types.Build) (string, error) {
agent, err := getAgent(db, rebind(stmtAgentSelectAgentCommit), build.ID)
if err != nil {
return "", err
}
return agent.Addr, nil
}
// SetAgent updates an agent in the datastore.
func (db *Agentstore) SetAgent(build *types.Build, addr string) error {
agent := Agent{Addr: addr, BuildID: build.ID}
return createAgent(db, rebind(stmtAgentInsert), &agent)
}
type Agent struct {
ID int64
Addr string
BuildID int64 `sql:"unique:ux_agent_build"`
}

View file

@ -1,184 +0,0 @@
package builtin
// DO NOT EDIT
// code generated by go:generate
import (
"database/sql"
"encoding/json"
)
var _ = json.Marshal
// generic database interface, matching both *sql.Db and *sql.Tx
type agentDB interface {
Exec(query string, args ...interface{}) (sql.Result, error)
Query(query string, args ...interface{}) (*sql.Rows, error)
QueryRow(query string, args ...interface{}) *sql.Row
}
func getAgent(db agentDB, query string, args ...interface{}) (*Agent, error) {
row := db.QueryRow(query, args...)
return scanAgent(row)
}
func getAgents(db agentDB, query string, args ...interface{}) ([]*Agent, error) {
rows, err := db.Query(query, args...)
if err != nil {
return nil, err
}
defer rows.Close()
return scanAgents(rows)
}
func createAgent(db agentDB, query string, v *Agent) error {
var v0 string
var v1 int64
v0 = v.Addr
v1 = v.BuildID
res, err := db.Exec(query,
&v0,
&v1,
)
if err != nil {
return err
}
v.ID, err = res.LastInsertId()
return err
}
func updateAgent(db agentDB, query string, v *Agent) error {
var v0 int64
var v1 string
var v2 int64
v0 = v.ID
v1 = v.Addr
v2 = v.BuildID
_, err := db.Exec(query,
&v1,
&v2,
&v0,
)
return err
}
func scanAgent(row *sql.Row) (*Agent, error) {
var v0 int64
var v1 string
var v2 int64
err := row.Scan(
&v0,
&v1,
&v2,
)
if err != nil {
return nil, err
}
v := &Agent{}
v.ID = v0
v.Addr = v1
v.BuildID = v2
return v, nil
}
func scanAgents(rows *sql.Rows) ([]*Agent, error) {
var err error
var vv []*Agent
for rows.Next() {
var v0 int64
var v1 string
var v2 int64
err = rows.Scan(
&v0,
&v1,
&v2,
)
if err != nil {
return vv, err
}
v := &Agent{}
v.ID = v0
v.Addr = v1
v.BuildID = v2
vv = append(vv, v)
}
return vv, rows.Err()
}
const stmtAgentSelectList = `
SELECT
agent_id
,agent_addr
,agent_build_id
FROM agents
`
const stmtAgentSelectRange = `
SELECT
agent_id
,agent_addr
,agent_commit_id
FROM agents
LIMIT ? OFFSET ?
`
const stmtAgentSelect = `
SELECT
agent_id
,agent_addr
,agent_commit_id
FROM agents
WHERE agent_id = ?
`
const stmtAgentSelectAgentCommit = `
SELECT
agent_id
,agent_addr
,agent_commit_id
FROM agents
WHERE agent_commit_id = ?
`
const stmtAgentSelectCount = `
SELECT count(1)
FROM agents
`
const stmtAgentInsert = `
INSERT INTO agents (
agent_addr
,agent_commit_id
) VALUES (?,?);
`
const stmtAgentUpdate = `
UPDATE agents SET
agent_addr = ?
,agent_commit_id = ?
WHERE agent_id = ?
`
const stmtAgentDelete = `
DELETE FROM agents
WHERE agent_id = ?
`
const stmtAgentTable = `
CREATE TABLE IF NOT EXISTS agents (
agent_id INTEGER PRIMARY KEY AUTOINCREMENT
,agent_addr VARCHAR
,agent_commit_idINTEGER
);
`
const stmtAgentAgentCommitIndex = `
CREATE UNIQUE INDEX IF NOT EXISTS ux_agent_commit ON agents (agent_commit_id);
`

View file

@ -1,85 +0,0 @@
package builtin
import (
"bytes"
"database/sql"
"io"
"io/ioutil"
)
type Blob struct {
ID int64
Path string `sql:"unique:ux_blob_path"`
Data []byte
}
type Blobstore struct {
*sql.DB
}
// Del removes an object from the blobstore.
func (db *Blobstore) DelBlob(path string) error {
blob, _ := getBlob(db, rebind(stmtBlobSelectBlobPath), path)
if blob == nil {
return nil
}
_, err := db.Exec(rebind(stmtBlobDelete), blob.ID)
return err
}
// Get retrieves an object from the blobstore.
func (db *Blobstore) GetBlob(path string) ([]byte, error) {
blob, err := getBlob(db, rebind(stmtBlobSelectBlobPath), path)
if err != nil {
return nil, nil
}
return blob.Data, nil
}
// GetBlobReader retrieves an object from the blobstore.
// It is the caller's responsibility to call Close on
// the ReadCloser when finished reading.
func (db *Blobstore) GetBlobReader(path string) (io.ReadCloser, error) {
var blob, err = db.GetBlob(path)
var buf = bytes.NewBuffer(blob)
return ioutil.NopCloser(buf), err
}
// SetBlob inserts an object into the blobstore.
func (db *Blobstore) SetBlob(path string, data []byte) error {
blob, _ := getBlob(db, rebind(stmtBlobSelectBlobPath), path)
if blob == nil {
blob = &Blob{}
}
blob.Path = path
blob.Data = data
if blob.ID == 0 {
return createBlob(db, rebind(stmtBlobInsert), blob)
}
return updateBlob(db, rebind(stmtBlobUpdate), blob)
}
// SetBlobReader inserts an object into the blobstore by
// consuming data from r until EOF.
func (db *Blobstore) SetBlobReader(path string, r io.Reader) error {
var data, _ = ioutil.ReadAll(r)
return db.SetBlob(path, data)
}
func NewBlobstore(db *sql.DB) *Blobstore {
return &Blobstore{db}
}
// Blob table name in database.
const blobTable = "blobs"
const blobQuery = `
SELECT *
FROM blobs
WHERE blob_path = ?;
`
const blobDeleteStmt = `
DELETE FROM blobs
WHERE blob_path = ?;
`

View file

@ -1,184 +0,0 @@
package builtin
// DO NOT EDIT
// code generated by go:generate
import (
"database/sql"
"encoding/json"
)
var _ = json.Marshal
// generic database interface, matching both *sql.Db and *sql.Tx
type blobDB interface {
Exec(query string, args ...interface{}) (sql.Result, error)
Query(query string, args ...interface{}) (*sql.Rows, error)
QueryRow(query string, args ...interface{}) *sql.Row
}
func getBlob(db blobDB, query string, args ...interface{}) (*Blob, error) {
row := db.QueryRow(query, args...)
return scanBlob(row)
}
func getBlobs(db blobDB, query string, args ...interface{}) ([]*Blob, error) {
rows, err := db.Query(query, args...)
if err != nil {
return nil, err
}
defer rows.Close()
return scanBlobs(rows)
}
func createBlob(db blobDB, query string, v *Blob) error {
var v0 string
var v1 []byte
v0 = v.Path
v1 = v.Data
res, err := db.Exec(query,
&v0,
&v1,
)
if err != nil {
return err
}
v.ID, err = res.LastInsertId()
return err
}
func updateBlob(db blobDB, query string, v *Blob) error {
var v0 int64
var v1 string
var v2 []byte
v0 = v.ID
v1 = v.Path
v2 = v.Data
_, err := db.Exec(query,
&v1,
&v2,
&v0,
)
return err
}
func scanBlob(row *sql.Row) (*Blob, error) {
var v0 int64
var v1 string
var v2 []byte
err := row.Scan(
&v0,
&v1,
&v2,
)
if err != nil {
return nil, err
}
v := &Blob{}
v.ID = v0
v.Path = v1
v.Data = v2
return v, nil
}
func scanBlobs(rows *sql.Rows) ([]*Blob, error) {
var err error
var vv []*Blob
for rows.Next() {
var v0 int64
var v1 string
var v2 []byte
err = rows.Scan(
&v0,
&v1,
&v2,
)
if err != nil {
return vv, err
}
v := &Blob{}
v.ID = v0
v.Path = v1
v.Data = v2
vv = append(vv, v)
}
return vv, rows.Err()
}
const stmtBlobSelectList = `
SELECT
blob_id
,blob_path
,blob_data
FROM blobs
`
const stmtBlobSelectRange = `
SELECT
blob_id
,blob_path
,blob_data
FROM blobs
LIMIT ? OFFSET ?
`
const stmtBlobSelect = `
SELECT
blob_id
,blob_path
,blob_data
FROM blobs
WHERE blob_id = ?
`
const stmtBlobSelectBlobPath = `
SELECT
blob_id
,blob_path
,blob_data
FROM blobs
WHERE blob_path = ?
`
const stmtBlobSelectCount = `
SELECT count(1)
FROM blobs
`
const stmtBlobInsert = `
INSERT INTO blobs (
blob_path
,blob_data
) VALUES (?,?);
`
const stmtBlobUpdate = `
UPDATE blobs SET
blob_path = ?
,blob_data = ?
WHERE blob_id = ?
`
const stmtBlobDelete = `
DELETE FROM blobs
WHERE blob_id = ?
`
const stmtBlobTable = `
CREATE TABLE IF NOT EXISTS blobs (
blob_id INTEGER PRIMARY KEY AUTOINCREMENT
,blob_path VARCHAR
,blob_data BLOB
);
`
const stmtBlobBlobPathIndex = `
CREATE UNIQUE INDEX IF NOT EXISTS ux_blob_path ON blobs (blob_path);
`

View file

@ -1,66 +0,0 @@
package builtin
import (
"bytes"
"io/ioutil"
"testing"
"github.com/drone/drone/Godeps/_workspace/src/github.com/franela/goblin"
)
func TestBlobstore(t *testing.T) {
db := mustConnectTest()
bs := NewBlobstore(db)
defer db.Close()
g := goblin.Goblin(t)
g.Describe("Blobstore", func() {
// before each test be sure to purge the package
// table data from the database.
g.BeforeEach(func() {
db.Exec("DELETE FROM blobs")
})
g.It("Should Set a Blob", func() {
err := bs.SetBlob("foo", []byte("bar"))
g.Assert(err == nil).IsTrue()
})
g.It("Should Set a Blob reader", func() {
var buf bytes.Buffer
buf.Write([]byte("bar"))
err := bs.SetBlobReader("foo", &buf)
g.Assert(err == nil).IsTrue()
})
g.It("Should Overwrite a Blob", func() {
bs.SetBlob("foo", []byte("bar"))
bs.SetBlob("foo", []byte("baz"))
blob, err := bs.GetBlob("foo")
g.Assert(err == nil).IsTrue()
g.Assert(string(blob)).Equal("baz")
})
g.It("Should Get a Blob", func() {
bs.SetBlob("foo", []byte("bar"))
blob, err := bs.GetBlob("foo")
g.Assert(err == nil).IsTrue()
g.Assert(string(blob)).Equal("bar")
})
g.It("Should Get a Blob reader", func() {
bs.SetBlob("foo", []byte("bar"))
r, _ := bs.GetBlobReader("foo")
blob, _ := ioutil.ReadAll(r)
g.Assert(string(blob)).Equal("bar")
})
g.It("Should Del a Blob", func() {
bs.SetBlob("foo", []byte("bar"))
err := bs.DelBlob("foo")
g.Assert(err == nil).IsTrue()
})
})
}

View file

@ -1,222 +0,0 @@
package builtin
import (
"database/sql"
"github.com/drone/drone/pkg/types"
)
type Buildstore struct {
*sql.DB
}
func NewBuildstore(db *sql.DB) *Buildstore {
return &Buildstore{db}
}
// Build gets a build by ID
func (db *Buildstore) Build(id int64) (*types.Build, error) {
return getBuild(db, rebind(stmtBuildSelect), id)
}
// BuildNumber gets the specified build number for the
// named repository and build number
func (db *Buildstore) BuildNumber(repo *types.Repo, seq int) (*types.Build, error) {
return getBuild(db, rebind(stmtBuildSelectBuildNumber), repo.ID, seq)
}
// BuildPullRequest gets the specific build for the
// named repository and pull request number
func (db *Buildstore) BuildPullRequestNumber(repo *types.Repo, seq int) (*types.Build, error) {
return getBuild(db, rebind(stmtBuildSelectPullRequestNumber), repo.ID, seq)
}
// BuildSha gets the specific build for the
// named repository and sha
func (db *Buildstore) BuildSha(repo *types.Repo, sha, branch string) (*types.Build, error) {
return getBuild(db, rebind(stmtBuildSelectSha), repo.ID, sha, branch)
}
// BuildLast gets the last executed build for the
// named repository.
func (db *Buildstore) BuildLast(repo *types.Repo, branch string) (*types.Build, error) {
return getBuild(db, rebind(buildLastQuery), repo.ID, branch)
}
// BuildList gets a list of recent builds for the
// named repository.
func (db *Buildstore) BuildList(repo *types.Repo, limit, offset int) ([]*types.Build, error) {
return getBuilds(db, rebind(buildListQuery), repo.ID, limit, offset)
}
// AddBuild inserts a new build in the datastore.
func (db *Buildstore) AddBuild(build *types.Build) error {
tx, err := db.Begin()
if err != nil {
return err
}
defer tx.Rollback()
// extract the next build number from the database
row := tx.QueryRow(rebind(buildNumberLast), build.RepoID)
if row != nil {
row.Scan(&build.Number)
}
build.Number = build.Number + 1 // increment
err = createBuild(tx, rebind(stmtBuildInsert), build)
if err != nil {
return err
}
for _, job := range build.Jobs {
job.BuildID = build.ID
err := createJob(tx, rebind(stmtJobInsert), job)
if err != nil {
return err
}
}
return tx.Commit()
}
// SetBuild updates an existing build and build jobs.
func (db *Buildstore) SetBuild(build *types.Build) error {
tx, err := db.Begin()
if err != nil {
return err
}
defer tx.Rollback()
err = updateBuild(tx, rebind(stmtBuildUpdate), build)
if err != nil {
return err
}
for _, job := range build.Jobs {
err = updateJob(tx, rebind(stmtJobUpdate), job)
if err != nil {
return err
}
}
return tx.Commit()
}
// KillBuilds updates all pending or started builds
// in the datastore settings the status to killed.
func (db *Buildstore) KillBuilds() error {
var _, err1 = db.Exec(rebind(buildKillStmt))
if err1 != nil {
return err1
}
var _, err2 = db.Exec(rebind(jobKillStmt))
return err2
}
const stmtBuildSelectPullRequestNumber = stmtBuildSelectList + `
WHERE build_repo_id = ?
AND build_pull_request_number = ?
ORDER BY build_number DESC
LIMIT 1
`
const stmtBuildSelectSha = stmtBuildSelectList + `
WHERE build_repo_id = ?
AND build_commit_sha = ?
AND build_commit_branch = ?
ORDER BY build_number DESC
LIMIT 1
`
// SQL query to retrieve the latest builds across all branches.
const buildListQuery = `
SELECT
build_id
,build_repo_id
,build_number
,build_status
,build_started
,build_finished
,build_commit_sha
,build_commit_ref
,build_commit_link
,build_commit_branch
,build_commit_message
,build_commit_timestamp
,build_commit_remote
,build_commit_author_login
,build_commit_author_email
,build_pull_request_number
,build_pull_request_title
,build_pull_request_link
,build_pull_request_base_sha
,build_pull_request_base_ref
,build_pull_request_base_link
,build_pull_request_base_branch
,build_pull_request_base_message
,build_pull_request_base_timestamp
,build_pull_request_base_remote
,build_pull_request_base_author_login
,build_pull_request_base_author_email
FROM builds
WHERE build_repo_id = ?
ORDER BY build_number DESC
LIMIT ? OFFSET ?
`
// SQL query to retrieve the most recent build.
// TODO exclude pull requests
const buildLastQuery = `
SELECT
build_id
,build_repo_id
,build_number
,build_status
,build_started
,build_finished
,build_commit_sha
,build_commit_ref
,build_commit_link
,build_commit_branch
,build_commit_message
,build_commit_timestamp
,build_commit_remote
,build_commit_author_login
,build_commit_author_email
,build_pull_request_number
,build_pull_request_title
,build_pull_request_link
,build_pull_request_base_sha
,build_pull_request_base_ref
,build_pull_request_base_link
,build_pull_request_base_branch
,build_pull_request_base_message
,build_pull_request_base_timestamp
,build_pull_request_base_remote
,build_pull_request_base_author_login
,build_pull_request_base_author_email
FROM builds
WHERE build_repo_id = ?
AND build_commit_branch = ?
ORDER BY build_number DESC
LIMIT 1
`
// SQL statement to cancel all running builds.
const buildKillStmt = `
UPDATE builds SET build_status = 'killed'
WHERE build_status IN ('pending', 'running');
`
// SQL statement to cancel all running build jobs.
const jobKillStmt = `
UPDATE jobs SET job_status = 'killed'
WHERE job_status IN ('pending', 'running');
`
// SQL statement to retrieve the latest sequential
// build number for a build
const buildNumberLast = `
SELECT MAX(build_number)
FROM builds
WHERE build_repo_id = ?
`

View file

@ -1,747 +0,0 @@
package builtin
// DO NOT EDIT
// code generated by go:generate
import (
"database/sql"
"encoding/json"
. "github.com/drone/drone/pkg/types"
)
var _ = json.Marshal
// generic database interface, matching both *sql.Db and *sql.Tx
type buildDB interface {
Exec(query string, args ...interface{}) (sql.Result, error)
Query(query string, args ...interface{}) (*sql.Rows, error)
QueryRow(query string, args ...interface{}) *sql.Row
}
func getBuild(db buildDB, query string, args ...interface{}) (*Build, error) {
row := db.QueryRow(query, args...)
return scanBuild(row)
}
func getBuilds(db buildDB, query string, args ...interface{}) ([]*Build, error) {
rows, err := db.Query(query, args...)
if err != nil {
return nil, err
}
defer rows.Close()
return scanBuilds(rows)
}
func createBuild(db buildDB, query string, v *Build) error {
var v0 int64
var v1 int
var v2 string
var v3 int64
var v4 int64
var v5 string
var v6 string
var v7 string
var v8 string
var v9 string
var v10 string
var v11 string
var v12 string
var v13 string
var v14 int
var v15 string
var v16 string
var v17 string
var v18 string
var v19 string
var v20 string
var v21 string
var v22 string
var v23 string
var v24 string
var v25 string
v0 = v.RepoID
v1 = v.Number
v2 = v.Status
v3 = v.Started
v4 = v.Finished
if v.Commit != nil {
v5 = v.Commit.Sha
v6 = v.Commit.Ref
v7 = v.Commit.Link
v8 = v.Commit.Branch
v9 = v.Commit.Message
v10 = v.Commit.Timestamp
v11 = v.Commit.Remote
if v.Commit.Author != nil {
v12 = v.Commit.Author.Login
v13 = v.Commit.Author.Email
}
}
if v.PullRequest != nil {
v14 = v.PullRequest.Number
v15 = v.PullRequest.Title
v16 = v.PullRequest.Link
if v.PullRequest.Base != nil {
v17 = v.PullRequest.Base.Sha
v18 = v.PullRequest.Base.Ref
v19 = v.PullRequest.Base.Link
v20 = v.PullRequest.Base.Branch
v21 = v.PullRequest.Base.Message
v22 = v.PullRequest.Base.Timestamp
v23 = v.PullRequest.Base.Remote
if v.PullRequest.Base.Author != nil {
v24 = v.PullRequest.Base.Author.Login
v25 = v.PullRequest.Base.Author.Email
}
}
}
res, err := db.Exec(query,
&v0,
&v1,
&v2,
&v3,
&v4,
&v5,
&v6,
&v7,
&v8,
&v9,
&v10,
&v11,
&v12,
&v13,
&v14,
&v15,
&v16,
&v17,
&v18,
&v19,
&v20,
&v21,
&v22,
&v23,
&v24,
&v25,
)
if err != nil {
return err
}
v.ID, err = res.LastInsertId()
return err
}
func updateBuild(db buildDB, query string, v *Build) error {
var v0 int64
var v1 int64
var v2 int
var v3 string
var v4 int64
var v5 int64
var v6 string
var v7 string
var v8 string
var v9 string
var v10 string
var v11 string
var v12 string
var v13 string
var v14 string
var v15 int
var v16 string
var v17 string
var v18 string
var v19 string
var v20 string
var v21 string
var v22 string
var v23 string
var v24 string
var v25 string
var v26 string
v0 = v.ID
v1 = v.RepoID
v2 = v.Number
v3 = v.Status
v4 = v.Started
v5 = v.Finished
if v.Commit != nil {
v6 = v.Commit.Sha
v7 = v.Commit.Ref
v8 = v.Commit.Link
v9 = v.Commit.Branch
v10 = v.Commit.Message
v11 = v.Commit.Timestamp
v12 = v.Commit.Remote
if v.Commit.Author != nil {
v13 = v.Commit.Author.Login
v14 = v.Commit.Author.Email
}
}
if v.PullRequest != nil {
v15 = v.PullRequest.Number
v16 = v.PullRequest.Title
v17 = v.PullRequest.Link
if v.PullRequest.Base != nil {
v18 = v.PullRequest.Base.Sha
v19 = v.PullRequest.Base.Ref
v20 = v.PullRequest.Base.Link
v21 = v.PullRequest.Base.Branch
v22 = v.PullRequest.Base.Message
v23 = v.PullRequest.Base.Timestamp
v24 = v.PullRequest.Base.Remote
if v.PullRequest.Base.Author != nil {
v25 = v.PullRequest.Base.Author.Login
v26 = v.PullRequest.Base.Author.Email
}
}
}
_, err := db.Exec(query,
&v1,
&v2,
&v3,
&v4,
&v5,
&v6,
&v7,
&v8,
&v9,
&v10,
&v11,
&v12,
&v13,
&v14,
&v15,
&v16,
&v17,
&v18,
&v19,
&v20,
&v21,
&v22,
&v23,
&v24,
&v25,
&v26,
&v0,
)
return err
}
func scanBuild(row *sql.Row) (*Build, error) {
var v0 int64
var v1 int64
var v2 int
var v3 string
var v4 int64
var v5 int64
var v6 string
var v7 string
var v8 string
var v9 string
var v10 string
var v11 string
var v12 string
var v13 string
var v14 string
var v15 int
var v16 string
var v17 string
var v18 string
var v19 string
var v20 string
var v21 string
var v22 string
var v23 string
var v24 string
var v25 string
var v26 string
err := row.Scan(
&v0,
&v1,
&v2,
&v3,
&v4,
&v5,
&v6,
&v7,
&v8,
&v9,
&v10,
&v11,
&v12,
&v13,
&v14,
&v15,
&v16,
&v17,
&v18,
&v19,
&v20,
&v21,
&v22,
&v23,
&v24,
&v25,
&v26,
)
if err != nil {
return nil, err
}
v := &Build{}
v.ID = v0
v.RepoID = v1
v.Number = v2
v.Status = v3
v.Started = v4
v.Finished = v5
v.Commit = &Commit{}
v.Commit.Sha = v6
v.Commit.Ref = v7
v.Commit.Link = v8
v.Commit.Branch = v9
v.Commit.Message = v10
v.Commit.Timestamp = v11
v.Commit.Remote = v12
v.Commit.Author = &Author{}
v.Commit.Author.Login = v13
v.Commit.Author.Email = v14
v.PullRequest = &PullRequest{}
v.PullRequest.Number = v15
v.PullRequest.Title = v16
v.PullRequest.Link = v17
v.PullRequest.Base = &Commit{}
v.PullRequest.Base.Sha = v18
v.PullRequest.Base.Ref = v19
v.PullRequest.Base.Link = v20
v.PullRequest.Base.Branch = v21
v.PullRequest.Base.Message = v22
v.PullRequest.Base.Timestamp = v23
v.PullRequest.Base.Remote = v24
v.PullRequest.Base.Author = &Author{}
v.PullRequest.Base.Author.Login = v25
v.PullRequest.Base.Author.Email = v26
return v, nil
}
func scanBuilds(rows *sql.Rows) ([]*Build, error) {
var err error
var vv []*Build
for rows.Next() {
var v0 int64
var v1 int64
var v2 int
var v3 string
var v4 int64
var v5 int64
var v6 string
var v7 string
var v8 string
var v9 string
var v10 string
var v11 string
var v12 string
var v13 string
var v14 string
var v15 int
var v16 string
var v17 string
var v18 string
var v19 string
var v20 string
var v21 string
var v22 string
var v23 string
var v24 string
var v25 string
var v26 string
err = rows.Scan(
&v0,
&v1,
&v2,
&v3,
&v4,
&v5,
&v6,
&v7,
&v8,
&v9,
&v10,
&v11,
&v12,
&v13,
&v14,
&v15,
&v16,
&v17,
&v18,
&v19,
&v20,
&v21,
&v22,
&v23,
&v24,
&v25,
&v26,
)
if err != nil {
return vv, err
}
v := &Build{}
v.ID = v0
v.RepoID = v1
v.Number = v2
v.Status = v3
v.Started = v4
v.Finished = v5
v.Commit = &Commit{}
v.Commit.Sha = v6
v.Commit.Ref = v7
v.Commit.Link = v8
v.Commit.Branch = v9
v.Commit.Message = v10
v.Commit.Timestamp = v11
v.Commit.Remote = v12
v.Commit.Author = &Author{}
v.Commit.Author.Login = v13
v.Commit.Author.Email = v14
v.PullRequest = &PullRequest{}
v.PullRequest.Number = v15
v.PullRequest.Title = v16
v.PullRequest.Link = v17
v.PullRequest.Base = &Commit{}
v.PullRequest.Base.Sha = v18
v.PullRequest.Base.Ref = v19
v.PullRequest.Base.Link = v20
v.PullRequest.Base.Branch = v21
v.PullRequest.Base.Message = v22
v.PullRequest.Base.Timestamp = v23
v.PullRequest.Base.Remote = v24
v.PullRequest.Base.Author = &Author{}
v.PullRequest.Base.Author.Login = v25
v.PullRequest.Base.Author.Email = v26
vv = append(vv, v)
}
return vv, rows.Err()
}
const stmtBuildSelectList = `
SELECT
build_id
,build_repo_id
,build_number
,build_status
,build_started
,build_finished
,build_commit_sha
,build_commit_ref
,build_commit_link
,build_commit_branch
,build_commit_message
,build_commit_timestamp
,build_commit_remote
,build_commit_author_login
,build_commit_author_email
,build_pull_request_number
,build_pull_request_title
,build_pull_request_link
,build_pull_request_base_sha
,build_pull_request_base_ref
,build_pull_request_base_link
,build_pull_request_base_branch
,build_pull_request_base_message
,build_pull_request_base_timestamp
,build_pull_request_base_remote
,build_pull_request_base_author_login
,build_pull_request_base_author_email
FROM builds
`
const stmtBuildSelectRange = `
SELECT
build_id
,build_repo_id
,build_number
,build_status
,build_started
,build_finished
,build_commit_sha
,build_commit_ref
,build_commit_link
,build_commit_branch
,build_commit_message
,build_commit_timestamp
,build_commit_remote
,build_commit_author_login
,build_commit_author_email
,build_pull_request_number
,build_pull_request_title
,build_pull_request_link
,build_pull_request_base_sha
,build_pull_request_base_ref
,build_pull_request_base_link
,build_pull_request_base_branch
,build_pull_request_base_message
,build_pull_request_base_timestamp
,build_pull_request_base_remote
,build_pull_request_base_author_login
,build_pull_request_base_author_email
FROM builds
LIMIT ? OFFSET ?
`
const stmtBuildSelect = `
SELECT
build_id
,build_repo_id
,build_number
,build_status
,build_started
,build_finished
,build_commit_sha
,build_commit_ref
,build_commit_link
,build_commit_branch
,build_commit_message
,build_commit_timestamp
,build_commit_remote
,build_commit_author_login
,build_commit_author_email
,build_pull_request_number
,build_pull_request_title
,build_pull_request_link
,build_pull_request_base_sha
,build_pull_request_base_ref
,build_pull_request_base_link
,build_pull_request_base_branch
,build_pull_request_base_message
,build_pull_request_base_timestamp
,build_pull_request_base_remote
,build_pull_request_base_author_login
,build_pull_request_base_author_email
FROM builds
WHERE build_id = ?
`
const stmtBuildSelectBuildRepoId = `
SELECT
build_id
,build_repo_id
,build_number
,build_status
,build_started
,build_finished
,build_commit_sha
,build_commit_ref
,build_commit_link
,build_commit_branch
,build_commit_message
,build_commit_timestamp
,build_commit_remote
,build_commit_author_login
,build_commit_author_email
,build_pull_request_number
,build_pull_request_title
,build_pull_request_link
,build_pull_request_base_sha
,build_pull_request_base_ref
,build_pull_request_base_link
,build_pull_request_base_branch
,build_pull_request_base_message
,build_pull_request_base_timestamp
,build_pull_request_base_remote
,build_pull_request_base_author_login
,build_pull_request_base_author_email
FROM builds
WHERE build_repo_id = ?
`
const stmtBuildSelectBuildNumber = `
SELECT
build_id
,build_repo_id
,build_number
,build_status
,build_started
,build_finished
,build_commit_sha
,build_commit_ref
,build_commit_link
,build_commit_branch
,build_commit_message
,build_commit_timestamp
,build_commit_remote
,build_commit_author_login
,build_commit_author_email
,build_pull_request_number
,build_pull_request_title
,build_pull_request_link
,build_pull_request_base_sha
,build_pull_request_base_ref
,build_pull_request_base_link
,build_pull_request_base_branch
,build_pull_request_base_message
,build_pull_request_base_timestamp
,build_pull_request_base_remote
,build_pull_request_base_author_login
,build_pull_request_base_author_email
FROM builds
WHERE build_repo_id = ?
AND build_number = ?
`
const stmtBuildSelectCommitBranch = `
SELECT
build_id
,build_repo_id
,build_number
,build_status
,build_started
,build_finished
,build_commit_sha
,build_commit_ref
,build_commit_link
,build_commit_branch
,build_commit_message
,build_commit_timestamp
,build_commit_remote
,build_commit_author_login
,build_commit_author_email
,build_pull_request_number
,build_pull_request_title
,build_pull_request_link
,build_pull_request_base_sha
,build_pull_request_base_ref
,build_pull_request_base_link
,build_pull_request_base_branch
,build_pull_request_base_message
,build_pull_request_base_timestamp
,build_pull_request_base_remote
,build_pull_request_base_author_login
,build_pull_request_base_author_email
FROM builds
WHERE build_branch = ?
AND build_branch = ?
`
const stmtBuildSelectCount = `
SELECT count(1)
FROM builds
`
const stmtBuildInsert = `
INSERT INTO builds (
build_repo_id
,build_number
,build_status
,build_started
,build_finished
,build_commit_sha
,build_commit_ref
,build_commit_link
,build_commit_branch
,build_commit_message
,build_commit_timestamp
,build_commit_remote
,build_commit_author_login
,build_commit_author_email
,build_pull_request_number
,build_pull_request_title
,build_pull_request_link
,build_pull_request_base_sha
,build_pull_request_base_ref
,build_pull_request_base_link
,build_pull_request_base_branch
,build_pull_request_base_message
,build_pull_request_base_timestamp
,build_pull_request_base_remote
,build_pull_request_base_author_login
,build_pull_request_base_author_email
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);
`
const stmtBuildUpdate = `
UPDATE builds SET
build_repo_id = ?
,build_number = ?
,build_status = ?
,build_started = ?
,build_finished = ?
,build_commit_sha = ?
,build_commit_ref = ?
,build_commit_link = ?
,build_commit_branch = ?
,build_commit_message = ?
,build_commit_timestamp = ?
,build_commit_remote = ?
,build_commit_author_login = ?
,build_commit_author_email = ?
,build_pull_request_number = ?
,build_pull_request_title = ?
,build_pull_request_link = ?
,build_pull_request_base_sha = ?
,build_pull_request_base_ref = ?
,build_pull_request_base_link = ?
,build_pull_request_base_branch = ?
,build_pull_request_base_message = ?
,build_pull_request_base_timestamp = ?
,build_pull_request_base_remote = ?
,build_pull_request_base_author_login = ?
,build_pull_request_base_author_email = ?
WHERE build_id = ?
`
const stmtBuildDelete = `
DELETE FROM builds
WHERE build_id = ?
`
const stmtBuildTable = `
CREATE TABLE IF NOT EXISTS builds (
build_id INTEGER PRIMARY KEY AUTOINCREMENT
,build_repo_id INTEGER
,build_number INTEGER
,build_status VARCHAR
,build_started INTEGER
,build_finished INTEGER
,build_commit_sha VARCHAR
,build_commit_ref VARCHAR
,build_commit_link VARCHAR
,build_commit_branch VARCHAR
,build_commit_message VARCHAR
,build_commit_timestamp VARCHAR
,build_commit_remote VARCHAR
,build_commit_author_login VARCHAR
,build_commit_author_email VARCHAR
,build_pull_request_number INTEGER
,build_pull_request_title VARCHAR
,build_pull_request_link VARCHAR
,build_pull_request_base_sha VARCHAR
,build_pull_request_base_ref VARCHAR
,build_pull_request_base_link VARCHAR
,build_pull_request_base_branch VARCHAR
,build_pull_request_base_message VARCHAR
,build_pull_request_base_timestamp VARCHAR
,build_pull_request_base_remote VARCHAR
,build_pull_request_base_author_login VARCHAR
,build_pull_request_base_author_email VARCHAR
);
`
const stmtBuildBuildRepoIdIndex = `
CREATE INDEX IF NOT EXISTS ix_build_repo_id ON builds (build_repo_id);
`
const stmtBuildBuildNumberIndex = `
CREATE UNIQUE INDEX IF NOT EXISTS ux_build_number ON builds (build_repo_id,build_number);
`
const stmtBuildCommitBranchIndex = `
CREATE INDEX IF NOT EXISTS ix_commit_branch ON builds (build_branch,build_branch);
`

View file

@ -1,172 +0,0 @@
package builtin
import (
"testing"
"github.com/drone/drone/Godeps/_workspace/src/github.com/franela/goblin"
"github.com/drone/drone/pkg/types"
)
func TestCommitstore(t *testing.T) {
db := mustConnectTest()
bs := NewBuildstore(db)
defer db.Close()
g := goblin.Goblin(t)
g.Describe("Buildstore", func() {
// before each test be sure to purge the package
// table data from the database.
g.BeforeEach(func() {
db.Exec("DELETE FROM builds")
db.Exec("DELETE FROM jobs")
})
g.It("Should Post a Build", func() {
build := types.Build{
RepoID: 1,
Status: types.StateSuccess,
Commit: &types.Commit{
Ref: "refs/heads/master",
Sha: "85f8c029b902ed9400bc600bac301a0aadb144ac",
},
}
err := bs.AddBuild(&build)
g.Assert(err == nil).IsTrue()
g.Assert(build.ID != 0).IsTrue()
g.Assert(build.Number).Equal(1)
g.Assert(build.Commit.Ref).Equal("refs/heads/master")
g.Assert(build.Commit.Sha).Equal("85f8c029b902ed9400bc600bac301a0aadb144ac")
})
g.It("Should Put a Build", func() {
build := types.Build{
RepoID: 1,
Number: 5,
Status: types.StatePending,
Commit: &types.Commit{
Ref: "refs/heads/master",
Sha: "85f8c029b902ed9400bc600bac301a0aadb144ac",
},
}
bs.AddBuild(&build)
build.Status = types.StateRunning
err1 := bs.SetBuild(&build)
getbuild, err2 := bs.Build(build.ID)
g.Assert(err1 == nil).IsTrue()
g.Assert(err2 == nil).IsTrue()
g.Assert(build.ID).Equal(getbuild.ID)
g.Assert(build.RepoID).Equal(getbuild.RepoID)
g.Assert(build.Status).Equal(getbuild.Status)
g.Assert(build.Number).Equal(getbuild.Number)
})
g.It("Should Get a Build", func() {
build := types.Build{
RepoID: 1,
Status: types.StateSuccess,
}
bs.AddBuild(&build)
getbuild, err := bs.Build(build.ID)
g.Assert(err == nil).IsTrue()
g.Assert(build.ID).Equal(getbuild.ID)
g.Assert(build.RepoID).Equal(getbuild.RepoID)
g.Assert(build.Status).Equal(getbuild.Status)
})
g.It("Should Get a Build by Number", func() {
build1 := &types.Build{
RepoID: 1,
Status: types.StatePending,
}
build2 := &types.Build{
RepoID: 1,
Status: types.StatePending,
}
err1 := bs.AddBuild(build1)
err2 := bs.AddBuild(build2)
getbuild, err3 := bs.BuildNumber(&types.Repo{ID: 1}, build2.Number)
g.Assert(err1 == nil).IsTrue()
g.Assert(err2 == nil).IsTrue()
g.Assert(err3 == nil).IsTrue()
g.Assert(build2.ID).Equal(getbuild.ID)
g.Assert(build2.RepoID).Equal(getbuild.RepoID)
g.Assert(build2.Number).Equal(getbuild.Number)
})
g.It("Should Kill Pending or Started Builds", func() {
build1 := &types.Build{
RepoID: 1,
Status: types.StateRunning,
}
build2 := &types.Build{
RepoID: 1,
Status: types.StatePending,
}
bs.AddBuild(build1)
bs.AddBuild(build2)
err1 := bs.KillBuilds()
getbuild1, err2 := bs.Build(build1.ID)
getbuild2, err3 := bs.Build(build2.ID)
g.Assert(err1 == nil).IsTrue()
g.Assert(err2 == nil).IsTrue()
g.Assert(err3 == nil).IsTrue()
g.Assert(getbuild1.Status).Equal(types.StateKilled)
g.Assert(getbuild2.Status).Equal(types.StateKilled)
})
g.It("Should get recent Builds", func() {
build1 := &types.Build{
RepoID: 1,
Status: types.StateFailure,
}
build2 := &types.Build{
RepoID: 1,
Status: types.StateSuccess,
}
bs.AddBuild(build1)
bs.AddBuild(build2)
builds, err := bs.BuildList(&types.Repo{ID: 1}, 20, 0)
g.Assert(err == nil).IsTrue()
g.Assert(len(builds)).Equal(2)
g.Assert(builds[0].ID).Equal(build2.ID)
g.Assert(builds[0].RepoID).Equal(build2.RepoID)
g.Assert(builds[0].Status).Equal(build2.Status)
})
//
// g.It("Should get the last Commit", func() {
// commit1 := &types.Commit{
// RepoID: 1,
// State: types.StateFailure,
// Branch: "master",
// Ref: "refs/heads/master",
// Sha: "85f8c029b902ed9400bc600bac301a0aadb144ac",
// }
// commit2 := &types.Commit{
// RepoID: 1,
// State: types.StateFailure,
// Branch: "master",
// Ref: "refs/heads/master",
// Sha: "8d6a233744a5dcacbf2605d4592a4bfe8b37320d",
// }
// commit3 := &types.Commit{
// RepoID: 1,
// State: types.StateSuccess,
// Branch: "dev",
// Ref: "refs/heads/dev",
// Sha: "85f8c029b902ed9400bc600bac301a0aadb144ac",
// }
// err1 := bs.AddCommit(commit1)
// err2 := bs.AddCommit(commit2)
// err3 := bs.AddCommit(commit3)
// last, err4 := bs.CommitLast(&types.Repo{ID: 1}, "master")
// g.Assert(err1 == nil).IsTrue()
// g.Assert(err2 == nil).IsTrue()
// g.Assert(err3 == nil).IsTrue()
// g.Assert(err4 == nil).IsTrue()
// g.Assert(last.ID).Equal(commit2.ID)
// g.Assert(last.RepoID).Equal(commit2.RepoID)
// g.Assert(last.Sequence).Equal(commit2.Sequence)
// })
})
}

View file

@ -1,40 +0,0 @@
package builtin
import (
"database/sql"
"github.com/drone/drone/pkg/types"
)
type Jobstore struct {
*sql.DB
}
func NewJobstore(db *sql.DB) *Jobstore {
return &Jobstore{db}
}
// Job returns a Job by ID.
func (db *Jobstore) Job(id int64) (*types.Job, error) {
return getJob(db, rebind(stmtJobSelect), id)
}
// JobNumber returns a job by sequence number.
func (db *Jobstore) JobNumber(build *types.Build, seq int) (*types.Job, error) {
return getJob(db, rebind(stmtJobSelectBuildNumber), build.ID, seq)
}
// JobList returns a list of all build jobs
func (db *Jobstore) JobList(build *types.Build) ([]*types.Job, error) {
return getJobs(db, rebind(stmtJobSelectJobBuildId), build.ID)
}
// SetJob updates an existing build job.
func (db *Jobstore) SetJob(job *types.Job) error {
return updateJob(db, rebind(stmtJobUpdate), job)
}
// AddJob inserts a build job.
func (db *Jobstore) AddJob(job *types.Job) error {
return createJob(db, rebind(stmtJobInsert), job)
}

View file

@ -1,300 +0,0 @@
package builtin
// DO NOT EDIT
// code generated by go:generate
import (
"database/sql"
"encoding/json"
. "github.com/drone/drone/pkg/types"
)
var _ = json.Marshal
// generic database interface, matching both *sql.Db and *sql.Tx
type jobDB interface {
Exec(query string, args ...interface{}) (sql.Result, error)
Query(query string, args ...interface{}) (*sql.Rows, error)
QueryRow(query string, args ...interface{}) *sql.Row
}
func getJob(db jobDB, query string, args ...interface{}) (*Job, error) {
row := db.QueryRow(query, args...)
return scanJob(row)
}
func getJobs(db jobDB, query string, args ...interface{}) ([]*Job, error) {
rows, err := db.Query(query, args...)
if err != nil {
return nil, err
}
defer rows.Close()
return scanJobs(rows)
}
func createJob(db jobDB, query string, v *Job) error {
var v0 int64
var v1 int
var v2 string
var v3 int
var v4 int64
var v5 int64
var v6 []byte
v0 = v.BuildID
v1 = v.Number
v2 = v.Status
v3 = v.ExitCode
v4 = v.Started
v5 = v.Finished
v6, _ = json.Marshal(v.Environment)
res, err := db.Exec(query,
&v0,
&v1,
&v2,
&v3,
&v4,
&v5,
&v6,
)
if err != nil {
return err
}
v.ID, err = res.LastInsertId()
return err
}
func updateJob(db jobDB, query string, v *Job) error {
var v0 int64
var v1 int64
var v2 int
var v3 string
var v4 int
var v5 int64
var v6 int64
var v7 []byte
v0 = v.ID
v1 = v.BuildID
v2 = v.Number
v3 = v.Status
v4 = v.ExitCode
v5 = v.Started
v6 = v.Finished
v7, _ = json.Marshal(v.Environment)
_, err := db.Exec(query,
&v1,
&v2,
&v3,
&v4,
&v5,
&v6,
&v7,
&v0,
)
return err
}
func scanJob(row *sql.Row) (*Job, error) {
var v0 int64
var v1 int64
var v2 int
var v3 string
var v4 int
var v5 int64
var v6 int64
var v7 []byte
err := row.Scan(
&v0,
&v1,
&v2,
&v3,
&v4,
&v5,
&v6,
&v7,
)
if err != nil {
return nil, err
}
v := &Job{}
v.ID = v0
v.BuildID = v1
v.Number = v2
v.Status = v3
v.ExitCode = v4
v.Started = v5
v.Finished = v6
json.Unmarshal(v7, &v.Environment)
return v, nil
}
func scanJobs(rows *sql.Rows) ([]*Job, error) {
var err error
var vv []*Job
for rows.Next() {
var v0 int64
var v1 int64
var v2 int
var v3 string
var v4 int
var v5 int64
var v6 int64
var v7 []byte
err = rows.Scan(
&v0,
&v1,
&v2,
&v3,
&v4,
&v5,
&v6,
&v7,
)
if err != nil {
return vv, err
}
v := &Job{}
v.ID = v0
v.BuildID = v1
v.Number = v2
v.Status = v3
v.ExitCode = v4
v.Started = v5
v.Finished = v6
json.Unmarshal(v7, &v.Environment)
vv = append(vv, v)
}
return vv, rows.Err()
}
const stmtJobSelectList = `
SELECT
job_id
,job_build_id
,job_number
,job_status
,job_exit_code
,job_started
,job_finished
,job_environment
FROM jobs
`
const stmtJobSelectRange = `
SELECT
job_id
,job_build_id
,job_number
,job_status
,job_exit_code
,job_started
,job_finished
,job_environment
FROM jobs
LIMIT ? OFFSET ?
`
const stmtJobSelect = `
SELECT
job_id
,job_build_id
,job_number
,job_status
,job_exit_code
,job_started
,job_finished
,job_environment
FROM jobs
WHERE job_id = ?
`
const stmtJobSelectJobBuildId = `
SELECT
job_id
,job_build_id
,job_number
,job_status
,job_exit_code
,job_started
,job_finished
,job_environment
FROM jobs
WHERE job_build_id = ?
`
const stmtJobSelectBuildNumber = `
SELECT
job_id
,job_build_id
,job_number
,job_status
,job_exit_code
,job_started
,job_finished
,job_environment
FROM jobs
WHERE job_build_id = ?
AND job_number = ?
`
const stmtJobSelectCount = `
SELECT count(1)
FROM jobs
`
const stmtJobInsert = `
INSERT INTO jobs (
job_build_id
,job_number
,job_status
,job_exit_code
,job_started
,job_finished
,job_environment
) VALUES (?,?,?,?,?,?,?);
`
const stmtJobUpdate = `
UPDATE jobs SET
job_build_id = ?
,job_number = ?
,job_status = ?
,job_exit_code = ?
,job_started = ?
,job_finished = ?
,job_environment = ?
WHERE job_id = ?
`
const stmtJobDelete = `
DELETE FROM jobs
WHERE job_id = ?
`
const stmtJobTable = `
CREATE TABLE IF NOT EXISTS jobs (
job_id INTEGER PRIMARY KEY AUTOINCREMENT
,job_build_id INTEGER
,job_number INTEGER
,job_status VARCHAR(512)
,job_exit_code INTEGER
,job_started INTEGER
,job_finished INTEGER
,job_environmentVARCHAR(2048)
);
`
const stmtJobJobBuildIdIndex = `
CREATE INDEX IF NOT EXISTS ix_job_build_id ON jobs (job_build_id);
`
const stmtJobBuildNumberIndex = `
CREATE UNIQUE INDEX IF NOT EXISTS ux_build_number ON jobs (job_build_id,job_number);
`

View file

@ -1,119 +0,0 @@
package builtin
import (
"testing"
"github.com/drone/drone/Godeps/_workspace/src/github.com/franela/goblin"
"github.com/drone/drone/pkg/types"
)
func TestBuildstore(t *testing.T) {
db := mustConnectTest()
bs := NewJobstore(db)
cs := NewBuildstore(db)
defer db.Close()
g := goblin.Goblin(t)
g.Describe("Jobstore", func() {
// before each test we purge the package table data from the database.
g.BeforeEach(func() {
db.Exec("DELETE FROM jobs")
db.Exec("DELETE FROM builds")
})
g.It("Should Set a job", func() {
job := &types.Job{
BuildID: 1,
Status: "pending",
ExitCode: 0,
Number: 1,
}
err1 := bs.AddJob(job)
g.Assert(err1 == nil).IsTrue()
g.Assert(job.ID != 0).IsTrue()
job.Status = "started"
err2 := bs.SetJob(job)
g.Assert(err2 == nil).IsTrue()
getjob, err3 := bs.Job(job.ID)
g.Assert(err3 == nil).IsTrue()
g.Assert(getjob.Status).Equal(job.Status)
})
g.It("Should Get a Job by ID", func() {
job := &types.Job{
BuildID: 1,
Status: "pending",
ExitCode: 1,
Number: 1,
Environment: map[string]string{"foo": "bar"},
}
err1 := bs.AddJob(job)
g.Assert(err1 == nil).IsTrue()
g.Assert(job.ID != 0).IsTrue()
getjob, err2 := bs.Job(job.ID)
g.Assert(err2 == nil).IsTrue()
g.Assert(getjob.ID).Equal(job.ID)
g.Assert(getjob.Status).Equal(job.Status)
g.Assert(getjob.ExitCode).Equal(job.ExitCode)
g.Assert(getjob.Environment).Equal(job.Environment)
g.Assert(getjob.Environment["foo"]).Equal("bar")
})
g.It("Should Get a Job by Number", func() {
job := &types.Job{
BuildID: 1,
Status: "pending",
ExitCode: 1,
Number: 1,
}
err1 := bs.AddJob(job)
g.Assert(err1 == nil).IsTrue()
g.Assert(job.ID != 0).IsTrue()
getjob, err2 := bs.JobNumber(&types.Build{ID: 1}, 1)
g.Assert(err2 == nil).IsTrue()
g.Assert(getjob.ID).Equal(job.ID)
g.Assert(getjob.Status).Equal(job.Status)
})
g.It("Should Get a List of Jobs by Commit", func() {
build := types.Build{
RepoID: 1,
Status: types.StateSuccess,
Jobs: []*types.Job{
&types.Job{
BuildID: 1,
Status: "success",
ExitCode: 0,
Number: 1,
},
&types.Job{
BuildID: 3,
Status: "error",
ExitCode: 1,
Number: 2,
},
&types.Job{
BuildID: 5,
Status: "pending",
ExitCode: 0,
Number: 3,
},
},
}
//
err1 := cs.AddBuild(&build)
g.Assert(err1 == nil).IsTrue()
getjobs, err2 := bs.JobList(&build)
g.Assert(err2 == nil).IsTrue()
g.Assert(len(getjobs)).Equal(3)
g.Assert(getjobs[0].Number).Equal(1)
g.Assert(getjobs[0].Status).Equal(types.StateSuccess)
})
})
}

Some files were not shown because too many files have changed in this diff Show more