mirror of
https://github.com/harness/drone.git
synced 2025-05-12 06:59:54 +08:00
moved to single binary project structure
This commit is contained in:
parent
76ea19aca0
commit
155576fb03
@ -1 +0,0 @@
|
|||||||
This is where Drone binaries go after running "make" in the root directory.
|
|
@ -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 |
@ -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 |
@ -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>
|
|
@ -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);
|
|
||||||
})();
|
|
@ -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)
|
|
||||||
})();
|
|
@ -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);
|
|
||||||
})();
|
|
@ -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);
|
|
||||||
})();
|
|
@ -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);
|
|
||||||
|
|
||||||
})();
|
|
@ -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);
|
|
||||||
|
|
||||||
})();
|
|
@ -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)
|
|
||||||
|
|
||||||
})();
|
|
@ -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)
|
|
||||||
|
|
||||||
})();
|
|
@ -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);
|
|
||||||
})();
|
|
@ -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);
|
|
||||||
})();
|
|
@ -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);
|
|
||||||
})();
|
|
@ -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);
|
|
||||||
})();
|
|
@ -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);
|
|
||||||
})();
|
|
@ -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);
|
|
||||||
})();
|
|
@ -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;
|
|
||||||
|
|
||||||
})();
|
|
@ -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>
|
|
@ -1,8 +0,0 @@
|
|||||||
<ol>
|
|
||||||
<li>
|
|
||||||
<a href="/">
|
|
||||||
<i class="material-icons">arrow_back</i>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>{{owner}} / {{name}}</li>
|
|
||||||
</ol>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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"> </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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -1,8 +0,0 @@
|
|||||||
<ol>
|
|
||||||
<li>
|
|
||||||
<a href="/">
|
|
||||||
<i class="material-icons">arrow_back</i>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>Profile</li>
|
|
||||||
</ol>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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">[]({{ 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>
|
|
@ -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>
|
|
@ -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 <enter> 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>
|
|
@ -1 +0,0 @@
|
|||||||
<a href="/" class="logo"></a>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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 <enter> 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>
|
|
@ -1,8 +0,0 @@
|
|||||||
<ol>
|
|
||||||
<li>
|
|
||||||
<a href="/">
|
|
||||||
<i class="material-icons">arrow_back</i>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>Users</li>
|
|
||||||
</ol>
|
|
@ -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;
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
@ -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);
|
|
@ -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;
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
@ -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
|
|
||||||
*/
|
|
@ -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;
|
|
||||||
}
|
|
@ -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; }
|
|
@ -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);
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -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
|
|
||||||
// }
|
|
@ -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)
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -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)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
@ -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)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
@ -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
|
|
||||||
}
|
|
@ -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"`
|
|
||||||
}
|
|
@ -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);
|
|
||||||
`
|
|
@ -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 = ?;
|
|
||||||
`
|
|
@ -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);
|
|
||||||
`
|
|
@ -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()
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
@ -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 = ?
|
|
||||||
`
|
|
@ -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);
|
|
||||||
`
|
|
@ -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)
|
|
||||||
// })
|
|
||||||
})
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
@ -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);
|
|
||||||
`
|
|
@ -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
Loading…
Reference in New Issue
Block a user