From 0a0151fa5e988ec8e117c91fa66b40803b861e9c Mon Sep 17 00:00:00 2001 From: Ciaran Downey Date: Wed, 22 Oct 2014 22:26:14 -0700 Subject: [PATCH 01/11] Adjust Slack message format to match HipChat's Also add some tests for formatting. See #586. --- plugin/notify/notify_test.go | 28 ++++++++++++++ plugin/notify/slack.go | 13 ++++--- plugin/notify/slack_test.go | 72 +++++++++++++++++++++++++----------- 3 files changed, 87 insertions(+), 26 deletions(-) create mode 100644 plugin/notify/notify_test.go diff --git a/plugin/notify/notify_test.go b/plugin/notify/notify_test.go new file mode 100644 index 000000000..8faca4084 --- /dev/null +++ b/plugin/notify/notify_test.go @@ -0,0 +1,28 @@ +package notify + +import ( + "testing" + + "github.com/drone/drone/shared/model" +) + +func Test_getBuildUrl(t *testing.T) { + c := &model.Request{ + Host: "http://examplehost.com", + Repo: &model.Repo{ + Host: "examplegit.com", + Owner: "owner", + Name: "repo", + }, + Commit: &model.Commit{ + Sha: "abc", + Branch: "example", + }, + } + expected := "http://examplehost.com/examplegit.com/owner/repo/example/abc" + output := getBuildUrl(c) + + if output != expected { + t.Errorf("Failed to build url. Expected: %s, got %s", expected, output) + } +} diff --git a/plugin/notify/slack.go b/plugin/notify/slack.go index 9d20cee7e..22320b4bd 100644 --- a/plugin/notify/slack.go +++ b/plugin/notify/slack.go @@ -9,9 +9,9 @@ import ( const ( slackEndpoint = "https://%s.slack.com/services/hooks/incoming-webhook?token=%s" - slackStartedMessage = "*Building* %s, commit <%s|%s>, author %s" - slackSuccessMessage = "*Success* %s, commit <%s|%s>, author %s" - slackFailureMessage = "*Failed* %s, commit <%s|%s>, author %s" + slackStartedMessage = "*Building* <%s|%s> (%s) by %s" + slackSuccessMessage = "*Success* <%s|%s> (%s) by %s" + slackFailureMessage = "*Failed* <%s|%s> (%s) by %s" ) type Slack struct { @@ -39,11 +39,14 @@ func (s *Slack) Send(context *model.Request) error { func (s *Slack) getMessage(context *model.Request, message string) string { url := getBuildUrl(context) - return fmt.Sprintf(message, context.Repo.Name, url, context.Commit.ShaShort(), context.Commit.Author) + // drone/drone#3333333 + linktext := context.Repo.Owner + "/" + context.Repo.Name + "#" + context.Commit.ShaShort() + + return fmt.Sprintf(message, linktext, url, context.Commit.Branch, context.Commit.Author) } func (s *Slack) sendStarted(context *model.Request) error { - return s.send(s.getMessage(context, slackStartedMessage), "warning") + return s.send(s.getMessage(context, slackStartedMessage)+"\n - "+context.Commit.Message, "warning") } func (s *Slack) sendSuccess(context *model.Request) error { diff --git a/plugin/notify/slack_test.go b/plugin/notify/slack_test.go index 81493e11d..93ee35aea 100644 --- a/plugin/notify/slack_test.go +++ b/plugin/notify/slack_test.go @@ -1,27 +1,57 @@ package notify -import ( - "github.com/drone/drone/shared/model" - "testing" -) +import "testing" -func Test_getBuildUrl(t *testing.T) { - c := &model.Request{ - Host: "http://examplehost.com", - Repo: &model.Repo{ - Host: "examplegit.com", - Owner: "owner", - Name: "repo", - }, - Commit: &model.Commit{ - Sha: "abc", - Branch: "example", - }, - } - expected := "http://examplehost.com/examplegit.com/owner/repo/example/abc" - output := getBuildUrl(c) +/* +var request = &model.Request{ + Host: "http://examplehost.com", + Repo: &model.Repo{ + Host: "examplegit.com", + Owner: "owner", + Name: "repo", + }, + Commit: &model.Commit{ + Sha: "abc", + Branch: "example", + Status: "Started", + Message: "Test Commit", + Author: "Test User", + }, + User: &model.User{ + Login: "TestUser", + }, +} +*/ - if output != expected { - t.Errorf("Failed to build url. Expected: %s, got %s", expected, output) +var slackExpectedLink = "" +var slackExpectedBase = slackExpectedLink + " (example) by Test User" + +func Test_slackStartedMessage(t *testing.T) { + actual := (&Slack{}).getMessage(request, slackStartedMessage) + + expected := "*Building* " + slackExpectedBase + + if actual != expected { + t.Errorf("Invalid getStarted message for Slack. Expected %v, got %v", expected, actual) + } +} + +func Test_slackSuccessMessage(t *testing.T) { + actual := (&Slack{}).getMessage(request, slackSuccessMessage) + + expected := "*Success* " + slackExpectedBase + + if actual != expected { + t.Errorf("Invalid getStarted message for Slack. Expected %v, got %v", expected, actual) + } +} + +func Test_slackFailureMessage(t *testing.T) { + actual := (&Slack{}).getMessage(request, slackFailureMessage) + + expected := "*Failed* " + slackExpectedBase + + if actual != expected { + t.Errorf("Invalid getStarted message for Slack. Expected %v, got %v", expected, actual) } } From 7d9e74e950aa0118b5f20ea0ffc34047211cc630 Mon Sep 17 00:00:00 2001 From: Nils Werner Date: Fri, 24 Oct 2014 16:19:13 +0200 Subject: [PATCH 02/11] Wrap ssh cmd in quotes to prevent execution of &&-chained commands on local machine --- plugin/deploy/ssh.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/deploy/ssh.go b/plugin/deploy/ssh.go index 0799dd88f..0a9653a48 100644 --- a/plugin/deploy/ssh.go +++ b/plugin/deploy/ssh.go @@ -81,7 +81,7 @@ func (s *SSH) Write(f *buildfile.Buildfile) { } if len(s.Cmd) > 0 { - sshCmd := "ssh -o StrictHostKeyChecking=no -p %s %s %s" + sshCmd := "ssh -o StrictHostKeyChecking=no -p %s %s \"%s\"" f.WriteCmd(fmt.Sprintf(sshCmd, host[1], strings.SplitN(host[0], ":", 2)[0], s.Cmd)) } } From 3fa78ac0885eaa13c4d55ab583a606d7ea881b23 Mon Sep 17 00:00:00 2001 From: Nils Werner Date: Sat, 25 Oct 2014 15:04:22 +0200 Subject: [PATCH 03/11] Fix failing unittest --- plugin/deploy/ssh_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/deploy/ssh_test.go b/plugin/deploy/ssh_test.go index 581f15ccf..037d3ab34 100644 --- a/plugin/deploy/ssh_test.go +++ b/plugin/deploy/ssh_test.go @@ -70,7 +70,7 @@ func TestSSHNoArtifact(t *testing.T) { t.Error("Expect script not to contains scp command") } - if !strings.Contains(bscr, "ssh -o StrictHostKeyChecking=no -p 22 user@test.example.com /opt/bin/redeploy.sh") { + if !strings.Contains(bscr, "ssh -o StrictHostKeyChecking=no -p 22 user@test.example.com \"/opt/bin/redeploy.sh\"") { t.Error("Expect script to contains ssh command") } } From 9aa2134521e72a0cbe619e7948608416b2e13a32 Mon Sep 17 00:00:00 2001 From: Igor Dolzhikov Date: Sun, 26 Oct 2014 23:56:32 +0600 Subject: [PATCH 04/11] excluding of unused transformation to []byte A Readers are more flexible and result in faster code that uses less memory --- client/client.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/client/client.go b/client/client.go index 30c2ce7ba..d40d85f38 100644 --- a/client/client.go +++ b/client/client.go @@ -90,12 +90,8 @@ func (c *Client) run(method, path string, in, out interface{}) error { return err } - // Read the bytes from the body (make sure we defer close the body) + // make sure we defer close the body defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return err - } // Check for an http error status (ie not 200 StatusOK) switch resp.StatusCode { @@ -111,9 +107,9 @@ func (c *Client) run(method, path string, in, out interface{}) error { return ErrInternalServer } - // Unmarshall the JSON response + // Decode the JSON response if out != nil { - return json.Unmarshal(body, out) + return json.NewDecoder(resp.Body).Decode(out) } return nil From 7e9dcce3cb02a3c7f42c96dda1deb26af490d9d9 Mon Sep 17 00:00:00 2001 From: Igor Dolzhikov Date: Mon, 27 Oct 2014 00:47:54 +0600 Subject: [PATCH 05/11] second excluding of unused transformation sorry, could not grep it all before --- shared/build/docker/client.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/shared/build/docker/client.go b/shared/build/docker/client.go index 41fc48b7d..bc0171bd8 100644 --- a/shared/build/docker/client.go +++ b/shared/build/docker/client.go @@ -214,12 +214,8 @@ func (c *Client) do(method, path string, in, out interface{}) error { return err } - // Read the bytes from the body (make sure we defer close the body) + // make sure we defer close the body defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return err - } // Check for an http error status (ie not 200 StatusOK) switch resp.StatusCode { @@ -233,9 +229,9 @@ func (c *Client) do(method, path string, in, out interface{}) error { return ErrBadRequest } - // Unmarshall the JSON response + // Decode the JSON response if out != nil { - return json.Unmarshal(body, out) + return json.NewDecoder(resp.Body).Decode(out) } return nil From 4949608980325c0ab235a267c4a0fb94d5e2c516 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Mon, 27 Oct 2014 12:58:34 -0700 Subject: [PATCH 06/11] Affix the sidebar --- server/app/styles/drone.less | 5 +++ server/app/views/commit.html | 66 +++++++++++++++++++----------------- 2 files changed, 39 insertions(+), 32 deletions(-) diff --git a/server/app/styles/drone.less b/server/app/styles/drone.less index 1c913dae4..9c0703921 100644 --- a/server/app/styles/drone.less +++ b/server/app/styles/drone.less @@ -1229,6 +1229,11 @@ nav { //border-left:1px solid #cdcece; //box-shadow:-3px 0 0 rgba(0,0,0,.05); + #sidebar-inner { + position: fixed; + width: 180px; + } + h1 {font-size:28px; font-weight:300;} h2 {font-size:22px; font-weight:300; margin-bottom:20px;} dl {padding-top:23px; border-top:1px solid #ddd; margin-top:5px; diff --git a/server/app/views/commit.html b/server/app/views/commit.html index 6ca5465fd..fbdcc5258 100644 --- a/server/app/views/commit.html +++ b/server/app/views/commit.html @@ -1,37 +1,39 @@
From 459759370fa73264a4adfa4b21282471ab775945 Mon Sep 17 00:00:00 2001 From: Kirill Zaitsev Date: Mon, 27 Oct 2014 04:14:20 +0300 Subject: [PATCH 07/11] Show pull request link, when commit related with pull request --- server/app/views/commit.html | 29 +++++------------------- server/app/views/commit_detail.html | 23 +++++++++++++++++++ server/app/views/commit_detail_pr.html | 31 ++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 23 deletions(-) create mode 100644 server/app/views/commit_detail.html create mode 100644 server/app/views/commit_detail_pr.html diff --git a/server/app/views/commit.html b/server/app/views/commit.html index fbdcc5258..1f398e6c0 100644 --- a/server/app/views/commit.html +++ b/server/app/views/commit.html @@ -11,29 +11,12 @@

{{ commit.duration | toDuration}}

-
-
- - commit - {{ commit.sha | shortHash}} - to {{ commit.branch }} branch - -
-
- - commit - {{ commit.sha | shortHash}} - to {{ commit.branch }} branch - -
-
- commit {{ commit.sha | shortHash}} to {{ commit.branch }} branch -
-
{{ commit.finished_at | fromNow }}
-
Started {{ commit.started_at | fromNow }}
-
Created {{ commit.created_at}}
-
-
+
+
+
{{ commit.finished_at | fromNow }}
+
Started {{ commit.started_at | fromNow }}
+
Created {{ commit.created_at}}
+
diff --git a/server/app/views/commit_detail.html b/server/app/views/commit_detail.html new file mode 100644 index 000000000..639b73284 --- /dev/null +++ b/server/app/views/commit_detail.html @@ -0,0 +1,23 @@ + +
+ + commit + {{ commit.sha | shortHash}} + to {{ commit.branch }} branch + +
+ + +
+ + commit + {{ commit.sha | shortHash}} + to {{ commit.branch }} branch + +
+ + +
+ commit {{ commit.sha | shortHash}} to {{ commit.branch }} branch +
+ \ No newline at end of file diff --git a/server/app/views/commit_detail_pr.html b/server/app/views/commit_detail_pr.html new file mode 100644 index 000000000..6669a7b38 --- /dev/null +++ b/server/app/views/commit_detail_pr.html @@ -0,0 +1,31 @@ + +
+ + Pull Request + #{{ commit.pull_request }} + +
+ + +
+ + Pull Request + #{{ commit.pull_request }} + +
+ + +
+ + Pull Request + #{{ commit.pull_request }} + +
+ + +
+ + Pull Request #{{ commit.pull_request }} + +
+ \ No newline at end of file From c7649e15b1513501ee9b4ee71ba9899d6a642a0d Mon Sep 17 00:00:00 2001 From: Kirill Zaitsev Date: Mon, 27 Oct 2014 14:32:44 +0300 Subject: [PATCH 08/11] Add gitlab repo url --- plugin/remote/gitlab/gitlab.go | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin/remote/gitlab/gitlab.go b/plugin/remote/gitlab/gitlab.go index 61423297f..b4e07597d 100644 --- a/plugin/remote/gitlab/gitlab.go +++ b/plugin/remote/gitlab/gitlab.go @@ -75,6 +75,7 @@ func (r *Gitlab) GetRepos(user *model.User) ([]*model.Repo, error) { CloneURL: item.HttpRepoUrl, GitURL: item.HttpRepoUrl, SSHURL: item.SshRepoUrl, + URL: item.Url, Role: &model.Perm{}, } From a6a875d76c8e51934fcde91bea563470dad52097 Mon Sep 17 00:00:00 2001 From: Vsevolod Strukchinsky Date: Tue, 28 Oct 2014 14:44:43 +0500 Subject: [PATCH 09/11] Update drone.css to enable affix sidebar --- server/app/styles/drone.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/app/styles/drone.css b/server/app/styles/drone.css index fdc7f4b4f..030b48099 100644 --- a/server/app/styles/drone.css +++ b/server/app/styles/drone.css @@ -1 +1 @@ -html,body,div,span,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,abbr,address,cite,code,del,dfn,em,img,ins,kbd,q,samp,small,strong,sub,sup,var,b,i,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,figcaption,figure,footer,header,hgroup,menu,nav,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline;list-style:none}.hidden{display:none!important;visibility:hidden}.invisible{visibility:hidden}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{content:"";display:table}.clearfix:after{clear:both}.nowrap{white-space:nowrap}.border_box{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box}.fix3d{-webkit-transform:translate3D(0,0,0)}.border_box{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box}.invert{-webkit-filter:invert(100%);-moz-filter:invert(100%);-ms-filter:invert(100%);-o-filter:invert(100%);filter:invert(100%)}.ladda-button{position:relative;background:0 0;border:0;cursor:pointer;outline:0;-webkit-appearance:none;-webkit-tap-highlight-color:rgba(0,0,0,0)}.ladda-button[data-loading=true]{cursor:default}.ladda-button:disabled{opacity:1}.ladda-button,.ladda-button .spinner,.ladda-button .label{-webkit-transition:.3s cubic-bezier(0.175,.885,.32,1.275) all;-moz-transition:.3s cubic-bezier(0.175,.885,.32,1.275) all;-ms-transition:.3s cubic-bezier(0.175,.885,.32,1.275) all;transition:.3s cubic-bezier(0.175,.885,.32,1.275) all}.ladda-button.zoom-in,.ladda-button.zoom-in .spinner,.ladda-button.zoom-in .label,.ladda-button.zoom-out,.ladda-button.zoom-out .spinner,.ladda-button.zoom-out .label{-webkit-transition:.3s ease all;-moz-transition:.3s ease all;-ms-transition:.3s ease all;transition:.3s ease all}.ladda-button.expand-right .spinner{right:.8em}.ladda-button.expand-right[data-loading=true]{padding-right:56px!IMPORTANT}.ladda-button.expand-right[data-loading=true]:after{content:'';width:20px;height:20px;display:block;border-radius:50%;border:3px solid #fff;border-right-color:rgba(0,0,0,0);border-top-color:rgba(0,0,0,0);-webkit-animation:spin 1s linear infinite;-moz-animation:spin 1s linear infinite;-ms-animation:spin 1s linear infinite;-o-animation:spin 1s linear infinite;animation:spin 1s linear infinite;position:absolute;top:5px;right:5px}.ladda-button.expand-right[data-loading=true]{padding-right:56px!IMPORTANT}.ladda-button.expand-right[data-loading=true] .spinner{opacity:1}.ladda-button.expand-left .spinner{left:.8em}.ladda-button.expand-left[data-loading=true]{padding-left:56px!IMPORTANT}.ladda-button.expand-left[data-loading=true] .spinner{opacity:1}.ladda-button.expand-up{overflow:hidden}.ladda-button.expand-up .spinner{top:-32px;left:50%;margin-left:-16px}.ladda-button.expand-up[data-loading=true]{padding-top:3em}.ladda-button.expand-up[data-loading=true] .spinner{opacity:1;top:.8em;margin-top:0}.ladda-button.expand-down{overflow:hidden}.ladda-button.expand-down .spinner{top:3.3em;left:50%;margin-left:-16px}.ladda-button.expand-down[data-loading=true]{padding-bottom:3em}.ladda-button.expand-down[data-loading=true] .spinner{opacity:1}.ladda-button.slide-left{overflow:hidden}.ladda-button.slide-left .label{position:relative}.ladda-button.slide-left .spinner{left:100%;margin-left:-16px}.ladda-button.slide-left[data-loading=true] .label{opacity:0;left:-100%}.ladda-button.slide-left[data-loading=true] .spinner{opacity:1;left:50%}.ladda-button.slide-right{overflow:hidden}.ladda-button.slide-right .label{position:relative}.ladda-button.slide-right .spinner{right:100%;margin-left:-16px}.ladda-button.slide-right[data-loading=true] .label{opacity:0;left:100%}.ladda-button.slide-right[data-loading=true] .spinner{opacity:1;left:50%}.ladda-button.slide-up{overflow:hidden}.ladda-button.slide-up .label{position:relative}.ladda-button.slide-up .spinner{left:50%;margin-left:-16px;margin-top:1em}.ladda-button.slide-up[data-loading=true] .label{opacity:0;top:-1em}.ladda-button.slide-up[data-loading=true] .spinner{opacity:1;margin-top:-16px}.ladda-button.slide-down{overflow:hidden}.ladda-button.slide-down .label{position:relative}.ladda-button.slide-down .spinner{left:50%;margin-left:-16px;margin-top:-2em}.ladda-button.slide-down[data-loading=true] .label{opacity:0;top:1em}.ladda-button.slide-down[data-loading=true] .spinner{opacity:1;margin-top:-16px}.ladda-button.zoom-out{overflow:hidden}.ladda-button.zoom-out .spinner{left:50%;margin-left:-16px;-webkit-transform:scale(2.5);-moz-transform:scale(2.5);-ms-transform:scale(2.5);transform:scale(2.5)}.ladda-button.zoom-out .label{position:relative;display:inline-block}.ladda-button.zoom-out[data-loading=true] .label{opacity:0;-webkit-transform:scale(0.5);-moz-transform:scale(0.5);-ms-transform:scale(0.5);transform:scale(0.5)}.ladda-button.zoom-out[data-loading=true] .spinner{opacity:1;-webkit-transform:none;-moz-transform:none;-ms-transform:none;transform:none}.ladda-button.zoom-in{overflow:hidden}.ladda-button.zoom-in .spinner{left:50%;margin-left:-16px;-webkit-transform:scale(0.2);-moz-transform:scale(0.2);-ms-transform:scale(0.2);transform:scale(0.2)}.ladda-button.zoom-in .label{position:relative;display:inline-block}.ladda-button.zoom-in[data-loading=true] .label{opacity:0;-webkit-transform:scale(2.2);-moz-transform:scale(2.2);-ms-transform:scale(2.2);transform:scale(2.2)}.ladda-button.zoom-in[data-loading=true] .spinner{opacity:1;-webkit-transform:none;-moz-transform:none;-ms-transform:none;transform:none}.ladda-button.contract{overflow:hidden;width:100px}.ladda-button.contract .spinner{left:50%;margin-left:-16px}.ladda-button.contract[data-loading=true]{border-radius:50%;width:52px}.ladda-button.contract[data-loading=true] .label{opacity:0}.ladda-button.contract[data-loading=true] .spinner{opacity:1}.ladda-button.contract-overlay{overflow:hidden;width:100px;box-shadow:0 0 0 3000px rgba(0,0,0,0)}.ladda-button.contract-overlay .spinner{left:50%;margin-left:-16px}.ladda-button.contract-overlay[data-loading=true]{border-radius:50%;width:52px;box-shadow:0 0 0 3000px rgba(0,0,0,.8)}.ladda-button.contract-overlay[data-loading=true] .label{opacity:0}.ladda-button.contract-overlay[data-loading=true] .spinner{opacity:1}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg)}}@-moz-keyframes spin{0%{-moz-transform:rotate(0deg)}100%{-moz-transform:rotate(360deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0deg)}100%{-o-transform:rotate(360deg)}}@-ms-keyframes spin{0%{-ms-transform:rotate(0deg)}100%{-ms-transform:rotate(360deg)}}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}html{height:100%}body{font-family:'Open Sans';font-weight:400;margin:0;color:#212121;background:#fff;font-size:13px;line-height:1.3;-webkit-font-smoothing:antialiased;height:100%;position:relative}[ng\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak{display:none}#container{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;padding-top:55px;position:relative;min-width:100%;min-height:100%;display:flex;flex-direction:row-reverse;justify-content:space-between}#header{background:#212121;position:fixed;height:55px;top:0;left:0;right:0;z-index:9;color:#fff;font-size:15px;line-height:55px;text-align:center;box-shadow:1px 1px 4px rgba(0,0,0,.5)}#header .brand{display:inline-block;font-family:Orbitron;font-size:26px;line-height:55px;text-decoration:none;text-transform:uppercase;color:#CCC}#header .burger{position:absolute;top:0;left:31px;height:55px;font-size:22px;color:#CCC}#header .burger i.fa{line-height:55px}#header .login,#header .user{position:absolute;right:0;top:0;bottom:0;white-space:nowrap;margin-right:20px;display:inline-block}#header .login a,#header .user a{color:#CCC;text-decoration:none;text-transform:uppercase;line-height:55px;font-size:15px}#header .login a img,#header .user a img{border-radius:50%;float:right;width:32px;height:32px;margin-top:10px;margin-left:20px}#body{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;display:flex;min-width:100%;flex-direction:row-reverse;justify-content:space-between}#body article{width:100%}#drawer{visibility:hidden;position:fixed;z-index:10;left:0;top:55px;bottom:0;width:255px;background:#363636;-webkit-transition:all .2s;-moz-transition:all .2s;transition:all .2s;-webkit-transform:translate3d(-100%,0,0);-moz-transform:translate3d(0,0,0);transform:translate3d(-100%,0,0)}#drawer ul{margin-top:20px}#drawer ul a{color:#CCC;text-decoration:none;padding:10px 0 10px 30px;display:block;font-size:14px}#drawer ul a i{margin-right:10px;font-size:16px;opacity:.3;min-width:16px;display:inline-block}#drawer ul span.divider{display:block;height:1px;border-top:1px solid rgba(255,255,255,.1);margin-top:15px;margin-bottom:15px}#drawer .signout{position:absolute;bottom:20px;right:30px;color:#CCC;font-size:16px;text-transform:uppercase;text-decoration:none}#drawer .signout i{margin-left:20px}#drawer-checkbox{position:fixed;top:7px;left:10px;width:45px;height:40px;display:block;z-index:9999;opacity:0;background:0 0;border:none;cursor:pointer}#drawer-checkbox:checked~#drawer{visibility:visible;-webkit-transform:translate3d(0,0,0);-moz-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}#drawer-checkbox:checked~#drawer{visibility:visible;-webkit-transform:translate3d(0,0,0);-moz-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}#drawer-checkbox:checked~#header .fa-bars:before{content:"\f00d";color:#999}nav{padding-left:30px;background:#FFF;min-height:77px;max-height:77px;line-height:77px;font-family:'Open Sans';font-size:22px;white-space:nowrap;color:rgba(0,0,0,.7);border-bottom:1px solid #eee;position:relative;z-index:2}nav a{text-decoration:none;color:rgba(0,0,0,.7)}nav a:last-child{color:#000}nav a span.fa{margin-right:20px}nav div.options{float:right;margin-right:20px}nav div.options .pure-button{color:#FFF;text-transform:lowercase;font-size:14px;background:#f5f5f5;padding:10px 30px;color:rgba(0,0,0,.5)}nav div.options .pure-button i{margin-right:10px;margin-left:-10px}@supports (position:sticky){nav{position:sticky;top:55px}}@supports (position:-moz-sticky){nav{position:-moz-sticky;top:55px}}@supports (position:-webkit-sticky){nav{position:-webkit-sticky;top:55px}}.cards .card{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;padding-right:20px;padding-bottom:20px;padding-left:20px;text-decoration:none;position:relative;color:#212121;font-family:'Open Sans'}.cards .card[data-status=Success] em{border-top:5px solid #7cb342}.cards .card[data-status=Killed] em,.cards .card[data-status=Failure] em,.cards .card[data-status=Error] em{border-top:5px solid #f44336}.cards .card[data-status=Killed] .l-box,.cards .card[data-status=Failure] .l-box,.cards .card[data-status=Error] .l-box{border:1px solid #f44336}.cards .card[data-status=Killed] .l-box:hover,.cards .card[data-status=Failure] .l-box:hover,.cards .card[data-status=Error] .l-box:hover{border:1px solid #212121}.cards .card[data-status=Killed]:after,.cards .card[data-status=Failure]:after,.cards .card[data-status=Error]:after{font-family:FontAwesome;font-size:16px;position:absolute;right:12px;top:-8px;content:"\f111";color:#f44336;min-width:16px;text-align:center}.cards .card .l-box{background:#FFF;border:1px solid #DDD;position:relative;padding:30px 20px;height:200px;-webkit-transition:.4s border linear;-moz-transition:.4s border linear;-ms-transition:.4s border linear;-o-transition:.4s border linear;transition:.4s border linear;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box}.cards .card .l-box:hover{border:1px solid #212121}.cards .card .l-box em{position:absolute;bottom:20px;right:20px;left:20px;height:30px;line-height:30px;vertical-align:middle;text-align:right;padding-right:45px;padding-top:20px;font-size:14px;color:#666}.cards .card .l-box img{position:absolute;right:20px;bottom:20px;border-radius:50%;width:30px;height:30px}.cards .card .l-box .timeago{position:absolute;bottom:85px;left:25px;right:25px;text-align:right;font-size:14px;color:#849299;display:none}.cards .card h2{font-size:18px;font-weight:400;min-height:52px;max-height:52px;height:52px;text-align:center;vertical-align:middle;line-height:26px;color:#212121;text-overflow:ellipsis;-webkit-line-clamp:2;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.cards .card h2 .separator{margin:0}.cards .card.card-inactive .l-box{position:relative;box-shadow:none;background:#4ab1ce;color:#FFF;height:180px;border-color:#4ab1ce}.cards .card.card-inactive .l-box:hover{background:#3197b4}.cards .card.card-inactive .l-box:hover:before{background:#3197b4}.cards .card.card-inactive h2{padding-top:10px;color:#FFF}.cards .card.card-inactive em{position:absolute;border-top:1px solid rgba(255,255,255,.5);bottom:15px;font-size:13px;left:25px;right:25px;line-height:1.3;padding:0;padding-top:20px;text-align:center;display:block;height:30px;text-transform:uppercase;color:#FFF}.cards .card.card-browse-inactive,.cards .card.card-browse{text-align:center;color:#4ab1ce;font-size:16px;font-weight:700;text-transform:uppercase}.cards .card.card-browse-inactive .l-box,.cards .card.card-browse .l-box{padding-top:75px;background:#FFF;height:180px}.cards .card.card-browse-inactive .l-box{box-shadow:none}.cards .progressContainer{height:5px;background-color:#f44336;position:absolute;bottom:65px;left:20px;right:20px}.cards .progressContainer .activeProgress,.cards .progressContainer .secondaryProgress{position:absolute;top:0;left:0;bottom:0}.cards .progressContainer .activeProgress{background-color:#7cb342}.cards .progressContainer .secondaryProgress{background-color:#7cb342}#commitpage{max-width:1180px;margin:0 auto;margin-bottom:50px;margin-top:70px}#commitpage section{margin-top:30px}#commitpage section .commits{border:1px solid #DDD;border-bottom:0 solid #DDD;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box}#commitpage section .commits a{padding:20px 45px;display:block;border-bottom:1px solid #dadcdd;color:#212121;text-decoration:none;position:relative;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box}#commitpage section .commits a h2{font-family:'Open Sans';font-weight:700;font-size:16px;margin-bottom:5px}#commitpage section .commits a img{border-radius:50%;margin-right:10px;float:left;display:none}#commitpage section .commits a p{color:#363636;line-height:22px;vertical-align:middle}#commitpage section .commits a[data-status]:before{background:0 0;width:7px;min-width:7px;max-width:7px;position:absolute;left:-1px;top:0;bottom:0;text-align:left;color:#fff;font-size:20px;line-height:50px;font-family:'Open Sans';padding-left:2px;overflow:hidden;content:" "}#commitpage section .commits a[data-result=Killed],#commitpage section .commits a[data-status=Error],#commitpage section .commits a[data-status=Failure]{background:#fff9f5}#commitpage section .commits a[data-result=Killed]:before,#commitpage section .commits a[data-status=Error]:before,#commitpage section .commits a[data-status=Failure]:before{background:#f44336;content:"!"}#commitpage section .commits a[data-status=Success]:before{background:#7cb342}#commitpage .date span{display:inline-block;text-align:right;font-size:14px;width:100%;padding-right:30px;margin-top:15px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box}#setuppage .pure-g,#loginpage .pure-g{padding:30px;border:1px solid #DDD;max-width:400px;margin:0 auto;margin-top:50px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box}#setuppage .pure-g a,#loginpage .pure-g a{display:block;background:#45494b;color:#fff;padding:14px 20px;font-size:15px;border-radius:5px;text-decoration:none}#setuppage .pure-g a:hover,#loginpage .pure-g a:hover{background:#212121}#setuppage .pure-g [class*=fa-],#loginpage .pure-g [class*=fa-]{float:left;font-size:20px;position:relative;top:-3px;left:-3px;padding-right:10px;min-width:27px;min-height:20px}#setuppage .pure-g .pure-u-1 a,#loginpage .pure-g .pure-u-1 a{margin-bottom:10px}#setuppage .pure-g .pure-u-1:last-child a,#loginpage .pure-g .pure-u-1:last-child a{margin-bottom:0}#setuppage form.pure-g input[type=text],#loginpage form.pure-g input[type=text],#setuppage form.pure-g input[type=password],#loginpage form.pure-g input[type=password]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;width:100%;padding:8px;font-size:14px;margin-bottom:15px;border:1px solid #DDD}#setuppage form.pure-g input[type=submit],#loginpage form.pure-g input[type=submit]{display:block;background:#45494b;color:#fff;padding:14px 20px;font-size:15px;border-radius:5px;text-decoration:none;width:100%;border:none}#setuppage form.pure-g input[type=submit]:hover,#loginpage form.pure-g input[type=submit]:hover{background:#212121}#setuppage2{margin-bottom:50px}#setuppage2 section{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box}#setuppage2 section .pure-g{padding:30px;border:1px solid #DDD;max-width:400px;margin:0 auto;margin-top:50px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box}#setuppage2 section label{display:inline-block}#setuppage2 section input[type=text]{margin-top:5px;margin-bottom:10px;box-shadow:none;width:100%}#setuppage2 section .pure-button-primary{color:#FFF;background:#4ab1ce;padding:10px 20px;margin-top:20px;width:100%}#setuppage2 section .tip h2{font-size:16px;margin-bottom:20px}#setuppage2 section .tip dd{font-weight:700;color:#666;margin-top:15px;margin-bottom:5px}#setuppage2 section .tip dt{padding:.5em .6em;display:inline-block;border:1px solid #ccc;border-radius:4px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;width:100%}#syncpage{width:100%}#syncpage section{padding:40px 0 20px}#syncpage section h1{text-align:center;font-size:24px;margin-bottom:20px}#syncpage section h1 i{font-size:32px;margin-top:20px}#homepage{width:100%;background:#fafafa}#homepage section{padding:40px 0 20px}#homepage section h1{text-align:center;font-size:24px;margin-bottom:20px}#homepage section h1 i{font-size:32px;margin-top:20px}#homepage section div{max-width:1180px;margin:0 auto}#homepage section:nth-child(2){background:#FFF;padding:40px 0 20px}#homepage section:nth-child(3){border-bottom:0 solid #EEE}#homepage section .card[data-status=Killed] .l-box,#homepage section .card[data-status=Failure] .l-box,#homepage section .card[data-status=Error] .l-box{border:1px solid #f44336}#homepage section .card[data-status=Killed] .l-box:hover,#homepage section .card[data-status=Failure] .l-box:hover,#homepage section .card[data-status=Error] .l-box:hover{border:1px solid #212121}#repospage{width:100%}#repospage section{border-bottom:1px solid #eee;max-width:768px;margin:0 auto;margin-top:30px;margin-bottom:30px}#repospage section .search{margin-bottom:25px}#repospage section .search input[type=text],#repospage section .search input[type=search]{-webkit-transition:.4s border linear;-moz-transition:.4s border linear;-ms-transition:.4s border linear;-o-transition:.4s border linear;transition:.4s border linear;border:1px solid #ccc;border-radius:0;box-shadow:none;padding:12px}#repospage section .search input[type=text]:focus,#repospage section .search input[type=search]:focus{border-color:#129FEA}#repospage section .repo{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;text-decoration:none}#repospage section .repo:last-child>div{border-bottom:1px solid #fff}#repospage section .repo>div{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;border:1px solid #eee;border-bottom:1px solid #fff;-webkit-transition:.4s border linear;-moz-transition:.4s border linear;-ms-transition:.4s border linear;-o-transition:.4s border linear;transition:.4s border linear}#repospage section .repo>div>div{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;padding:20px 25px}#repospage section .repo>div>div:last-child div{text-align:right}#repospage section .repo>div:hover{border:1px solid #212121}#repospage section .repo h4{font-size:20px;margin-bottom:2px;color:#212121}#repospage section .repo span{color:#666;font-size:14px}#repospage section .repo i{color:#DDD;font-size:22px;margin-left:20px;margin-top:15px}#repospage section .repo i.fa-check-circle-o{color:#7cb342}#userspage{width:100%}#userspage section{border-bottom:1px solid #eee;max-width:768px;margin:0 auto;margin-top:30px;margin-bottom:30px}#userspage section .search{margin-bottom:25px}#userspage section .search input[type=text],#userspage section .search input[type=search]{-webkit-transition:.4s border linear;-moz-transition:.4s border linear;-ms-transition:.4s border linear;-o-transition:.4s border linear;transition:.4s border linear;border:1px solid #ccc;border-radius:0;box-shadow:none;padding:12px}#userspage section .search input[type=text]:focus,#userspage section .search input[type=search]:focus{border-color:#129FEA}#userspage section .user{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;text-decoration:none}#userspage section .user:last-child>div{border-bottom:1px solid #fff}#userspage section .user>div{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;border:1px solid #eee;border-bottom:1px solid #fff;-webkit-transition:.4s border linear;-moz-transition:.4s border linear;-ms-transition:.4s border linear;-o-transition:.4s border linear;transition:.4s border linear}#userspage section .user>div>div{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;padding:20px 25px;padding-right:0}#userspage section .user>div:hover{border:1px solid #212121}#userspage section .user img{border-radius:50%;width:48px;height:48px}#userspage section .user h4{font-size:20px;margin-bottom:2px;color:#212121}#userspage section .user h4 small{font-size:16px;color:#666;margin-left:5px}#userspage section .user span{color:#666;font-size:14px}#repoconfpage{width:100%}#repoconfpage section{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;border:1px solid #eee;max-width:768px;margin:0 auto;margin-top:30px;margin-bottom:30px;padding:20px}#repoconfpage section h2{font-size:16px;margin-bottom:15px}#repoconfpage section .markdown,#repoconfpage section .params,#repoconfpage section .key{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;min-height:50px;margin-top:10px;font-family:'Droid Sans Mono';border:1px solid #eee;padding:20px;width:100%;max-width:100%;color:#666}#repoconfpage section .markdown:focus,#repoconfpage section .params:focus,#repoconfpage section .key:focus{border-color:#129FEA;outline:0}#repoconfpage section .pure-button-primary{color:#FFF;background:#4ab1ce;padding:10px 20px;margin-top:20px}#repoconfpage section select,#repoconfpage section input[type=number]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;padding:8px;font-size:14px;margin-bottom:15px;border:1px solid #DDD}#repoconfpage section span.seconds{color:#212121;margin-left:10px}#repoconfpage section span.minutes{color:#212121}#repoconfpage section span.minutes:before{content:'('}#repoconfpage section span.minutes:after{content:')'}#accountpage{width:100%}#accountpage section{position:relative;max-width:768px;margin:0 auto;margin-top:30px;border:1px solid #eee}#accountpage section.profile>div:first-child{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;padding:20px;text-align:center}#accountpage section.profile>div:last-child{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;padding:20px}#accountpage section.profile .fullname{font-size:14px;margin-bottom:2px;color:#666;display:block}#accountpage section.profile .email{font-size:14px;color:#666;display:block}#accountpage section.token>div:first-child div{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;text-align:center;padding:20px;color:#666;font-size:16px;line-height:22px}#accountpage section.token>div:first-child i{margin-right:7px}#accountpage section.token>div:last-child{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;padding:20px;color:#666;line-height:22px;font-size:14px}#accountpage section h4{margin:10px 0;font-size:22px}#accountpage section h4 small{opacity:.6;font-size:16px;margin-left:10px}#accountpage section img{width:64px;height:64px;border-radius:50%}#accountpage section .notifications{position:absolute;top:0;right:0;margin:20px}#accountpage section .button-error{color:#FFF;background:#ca3c3c;padding:10px 20px;float:right}#accountpage section .pure-button-primary{color:#FFF;background:#4ab1ce;padding:10px 20px;margin-top:10px}#repopage{width:100%;background:#fafafa}#repopage section{padding:40px 0 20px}#repopage section>div{max-width:1180px;margin:0 auto}#repopage section:nth-child(even){background:#FFF}#repopage section:first-child{background:#FFF}#repopage section .card[data-status=Success]:nth-child(2) .l-box{border-color:#7cb342}#repopage section .card[data-status=Success]:nth-child(2) .l-box:hover{border:1px solid #212121}#repopage section .card[data-status=Killed]:nth-child(2) .l-box,#repopage section .card[data-status=Failure]:nth-child(2) .l-box,#repopage section .card[data-status=Error]:nth-child(2) .l-box{border-color:#f44336}#repopage section .card[data-status=Killed]:nth-child(2) .l-box:hover,#repopage section .card[data-status=Failure]:nth-child(2) .l-box:hover,#repopage section .card[data-status=Error]:nth-child(2) .l-box:hover{border:1px solid #212121}#repopage section .card[data-status=Started] em:before,#repopage section .card[data-status=Pending] em:before{-webkit-animation:progress 1s linear infinite;-moz-animation:progress 1s linear infinite;animation:progress 1s linear infinite;position:absolute;content:' ';height:5px;top:-5px;left:0;right:0;margin:0;background:#ffb300;background-image:-webkit-linear-gradient(-45deg,rgba(255,255,255,.55)25%,transparent 25%,transparent 50%,rgba(255,255,255,.55)50%,rgba(255,255,255,.55)75%,transparent 75%,transparent);background-image:linear-gradient(-45deg,rgba(255,255,255,.55)25%,transparent 25%,transparent 50%,rgba(255,255,255,.55)50%,rgba(255,255,255,.55)75%,transparent 75%,transparent);background-repeat:repeat-x;background-size:30px 30px}#repopage section .l-box em{line-height:19px;bottom:25px}#repopage section .l-box:after{font-family:FontAwesome;content:"\f104";content:"\f0d9";position:absolute;right:-20px;width:20px;text-align:center;color:rgba(0,0,0,.1);font-size:22px}#repopage section .card:last-child .l-box:after{content:''}#repopage section.nobuilds,#repopage section.inactive{text-align:center;padding-bottom:50px}#repopage section.nobuilds h1,#repopage section.inactive h1{font-size:26px;color:#212121}#repopage section.nobuilds p,#repopage section.inactive p{font-size:16px;color:#666}#repopage section.nobuilds i,#repopage section.inactive i{font-size:32px;margin-top:20px;margin-bottom:20px}#repopage section.nobuilds i.fa-file-code-o,#repopage section.inactive i.fa-file-code-o{font-size:42px;margin-top:30px}#repopage section.nobuilds .pure-button-primary,#repopage section.inactive .pure-button-primary{font-size:14px;text-transform:uppercase;background:#4ab1ce;padding:10px 30px}@-webkit-keyframes progress{to{background-position:30px 0}}@-moz-keyframes progress{to{background-position:30px 0}}@keyframes progress{to{background-position:30px 0}}#sidebar{width:240px;min-width:240px;position:relative;display:block;z-index:5;padding:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box}#sidebar h1{font-size:28px;font-weight:300}#sidebar h2{font-size:22px;font-weight:300;margin-bottom:20px}#sidebar dl{padding-top:23px;border-top:1px solid #ddd;margin-top:5px}#sidebar dl:first-child{padding-top:0;border-top:none;margin-top:0}#sidebar dl dt{font-size:12px;color:#849299;text-transform:uppercase;padding:3px 0}#sidebar dl dd{font-size:14px;padding:3px 0 20px}#sidebar dl a{text-transform:none}#sidebar dl small{font-size:12px}#sidebar dl .large{font-size:18px;padding-bottom:5px}#sidebar dl .large a{color:#212121}#sidebar dl .time{float:right;margin-left:8px}#sidebar dl .photo{margin-right:4px}#sidebar dl .negative{color:#f44336}#sidebar dl .photoline{display:inline-block;position:relative;top:-10px;font-weight:700}#sidebar dl .small{padding-bottom:5px;font-weight:700;font-size:12px}#sidebar .status{border:1px solid transparent;display:block;text-align:center;padding:5px 20px;border-radius:50px;text-transform:uppercase;margin:0 -5px 10px;font-weight:700}#sidebar .status:before{float:left;margin-left:-5px}#sidebar .status.status_ok{color:#7cb342;border-color:#7cb342}#sidebar .status.status_ok:before{content:"\f00c";font-family:FontAwesome}#sidebar .status.status_error{color:#f44336;border-color:#f44336}#sidebar .status.status_error:before{content:"!"}#sidebar .result{background:#4ab1ce;background:#7cb342;color:#fff;margin:-30px -30px -6px;padding:30px;position:relative}#sidebar .result .status{color:#fff;background:rgba(255,255,255,.2)}#sidebar .result .status:before{content:"\f00c";font-family:FontAwesome}#sidebar .result dl dd{padding:7px 0}#sidebar .result dl dd strong{font-size:16px}#sidebar .result[data-result=Killed],#sidebar .result[data-result=Failure],#sidebar .result[data-result=Error]{background:#f44336}#sidebar .result[data-result=Killed] .status:before,#sidebar .result[data-result=Failure] .status:before,#sidebar .result[data-result=Error] .status:before{content:"!"}#sidebar .result[data-result=Pending],#sidebar .result[data-result=Started]{background:#ffb300}#sidebar .result[data-result=Pending] .status:before,#sidebar .result[data-result=Started] .status:before{content:"\f021";-webkit-animation:spin 2s infinite linear;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;animation:spin 2s infinite linear}#main{flex-grow:2}#main.output{position:relative;background:#212121}#main.output pre{margin:0 auto;padding:30px;color:#FFF;font-family:'Droid Sans Mono';font-size:13px;white-space:pre-wrap;overflow:hidden;line-height:18px}#main.output #follow button{position:fixed;right:280px;bottom:21px;z-index:100;border-radius:7px;background-color:rgba(238,238,238,.2);padding:0 1em;cursor:pointer;font-family:'Open Sans';border:none;color:#fff}#main.output[data-result=Pending]:after,#main.output[data-result=Started]:after{position:absolute;right:20px;bottom:20px;color:#9e9e9e;font-size:18px;font-family:FontAwesome;content:"\f021";-webkit-animation:spin 2s infinite linear;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;animation:spin 2s infinite linear}.pure-form.pure-form-stacked label{font-size:14px;color:#666;margin-top:20px;margin-bottom:5px}.pure-form.pure-form-stacked label:first-child{margin-top:0}.pure-form.pure-form-stacked input[type=text],.pure-form.pure-form-stacked select{min-width:300px;box-shadow:none;padding:10px;border:1px solid #ccc;border-radius:0}.toggle{margin-bottom:10px}.toggle:nth-child(2){margin-top:40px}.toggle span{line-height:32px;vertical-align:middle;display:inline-block;margin-left:10px}.toggle input[type=checkbox]{max-height:0;max-width:0;opacity:0}.toggle input[type=checkbox]+label{display:inline-block;vertical-align:middle;position:relative;box-shadow:inset 0 0 0 1px #d5d5d5;text-indent:-5000px;height:30px;width:50px;border-radius:15px}.toggle input[type=checkbox]+label:before{content:"";position:absolute;display:block;height:30px;width:30px;top:0;left:0;border-radius:15px;background:rgba(19,191,17,0);-webkit-transition:.25s ease-in-out;-moz-transition:.25s ease-in-out;-ms-transition:.25s ease-in-out;-o-transition:.25s ease-in-out;transition:.25s ease-in-out}.toggle input[type=checkbox]+label:after{content:"";position:absolute;display:block;height:30px;width:30px;top:0;left:0;border-radius:15px;background:#fff;box-shadow:inset 0 0 0 1px rgba(0,0,0,.2),0 2px 4px rgba(0,0,0,.2);-webkit-transition:.25s ease-in-out;-moz-transition:.25s ease-in-out;-ms-transition:.25s ease-in-out;-o-transition:.25s ease-in-out;transition:.25s ease-in-out}.toggle input[type=checkbox]:checked+label:before{width:50px;background:#4ab1ce}.toggle input[type=checkbox]:checked+label:after{left:20px;box-shadow:inset 0 0 0 1px #4ab1ce,0 2px 4px rgba(0,0,0,.2)}.toast{position:fixed;bottom:50px;left:20%;right:20%;background:#363636;border-radius:3px;z-index:999;color:#FFF;padding:15px 20px;font-size:14px;box-shadow:2px 2px 2px rgba(0,0,0,.2)}.toast a,.toast a:visited,.toast a:hover,.toast a:active{color:#FFF}.toast button{float:right;background:0 0;border:none;color:#EEFF41;text-transform:uppercase;margin-left:10px}@media screen and (min-width:1180px){#repopage h2{padding-left:0}.cards .card{padding-left:0}}@media screen and (max-width:35.5em){.cards .card .l-box:after{display:none!IMPORTANT}header .username{display:none}#repopage h2{padding-left:20px}#userspage form,#repospage form{display:none}#userspage section,#repospage section{margin:0}#userspage section .user:nth-child(2)>.pure-g,#repospage section .user:nth-child(2)>.pure-g,#userspage section .repo:nth-child(2)>.pure-g,#repospage section .repo:nth-child(2)>.pure-g{border-top:1px solid #FFF}#userspage section .user:nth-child(2):hover>.pure-g,#repospage section .user:nth-child(2):hover>.pure-g,#userspage section .repo:nth-child(2):hover>.pure-g,#repospage section .repo:nth-child(2):hover>.pure-g{border-top:1px solid #212121}#userspage section .user i,#repospage section .user i,#userspage section .repo i,#repospage section .repo i{margin-left:0}#accountpage .token span{display:none}nav div.options .pure-button i{margin:0}nav div.options .pure-button span{display:none}} \ No newline at end of file +html,body,div,span,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,abbr,address,cite,code,del,dfn,em,img,ins,kbd,q,samp,small,strong,sub,sup,var,b,i,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,figcaption,figure,footer,header,hgroup,menu,nav,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline;list-style:none}.hidden{display:none!important;visibility:hidden}.invisible{visibility:hidden}.clearfix:before,.clearfix:after{content:"";display:table}.clearfix:after{clear:both}.nowrap{white-space:nowrap}.border_box{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box}.fix3d{-webkit-transform:translate3D(0,0,0)}.border_box{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box}.invert{-webkit-filter:invert(100%);-moz-filter:invert(100%);-ms-filter:invert(100%);-o-filter:invert(100%);filter:invert(100%)}.ladda-button{position:relative;background:0 0;border:0;cursor:pointer;outline:0;-webkit-appearance:none;-webkit-tap-highlight-color:transparent}.ladda-button[data-loading=true]{cursor:default}.ladda-button:disabled{opacity:1}.ladda-button,.ladda-button .spinner,.ladda-button .label{-webkit-transition:.3s cubic-bezier(0.175,.885,.32,1.275) all;-moz-transition:.3s cubic-bezier(0.175,.885,.32,1.275) all;-ms-transition:.3s cubic-bezier(0.175,.885,.32,1.275) all;transition:.3s cubic-bezier(0.175,.885,.32,1.275) all}.ladda-button.zoom-in,.ladda-button.zoom-in .spinner,.ladda-button.zoom-in .label,.ladda-button.zoom-out,.ladda-button.zoom-out .spinner,.ladda-button.zoom-out .label{-webkit-transition:.3s ease all;-moz-transition:.3s ease all;-ms-transition:.3s ease all;transition:.3s ease all}.ladda-button.expand-right .spinner{right:.8em}.ladda-button.expand-right[data-loading=true]{padding-right:56px!IMPORTANT}.ladda-button.expand-right[data-loading=true]:after{content:'';width:20px;height:20px;display:block;border-radius:50%;border:3px solid #fff;border-right-color:transparent;border-top-color:transparent;-webkit-animation:spin 1s linear infinite;-moz-animation:spin 1s linear infinite;-ms-animation:spin 1s linear infinite;-o-animation:spin 1s linear infinite;animation:spin 1s linear infinite;position:absolute;top:5px;right:5px}.ladda-button.expand-right[data-loading=true]{padding-right:56px!IMPORTANT}.ladda-button.expand-right[data-loading=true] .spinner{opacity:1}.ladda-button.expand-left .spinner{left:.8em}.ladda-button.expand-left[data-loading=true]{padding-left:56px!IMPORTANT}.ladda-button.expand-left[data-loading=true] .spinner{opacity:1}.ladda-button.expand-up{overflow:hidden}.ladda-button.expand-up .spinner{top:-32px;left:50%;margin-left:-16px}.ladda-button.expand-up[data-loading=true]{padding-top:3em}.ladda-button.expand-up[data-loading=true] .spinner{opacity:1;top:.8em;margin-top:0}.ladda-button.expand-down{overflow:hidden}.ladda-button.expand-down .spinner{top:3.3em;left:50%;margin-left:-16px}.ladda-button.expand-down[data-loading=true]{padding-bottom:3em}.ladda-button.expand-down[data-loading=true] .spinner{opacity:1}.ladda-button.slide-left{overflow:hidden}.ladda-button.slide-left .label{position:relative}.ladda-button.slide-left .spinner{left:100%;margin-left:-16px}.ladda-button.slide-left[data-loading=true] .label{opacity:0;left:-100%}.ladda-button.slide-left[data-loading=true] .spinner{opacity:1;left:50%}.ladda-button.slide-right{overflow:hidden}.ladda-button.slide-right .label{position:relative}.ladda-button.slide-right .spinner{right:100%;margin-left:-16px}.ladda-button.slide-right[data-loading=true] .label{opacity:0;left:100%}.ladda-button.slide-right[data-loading=true] .spinner{opacity:1;left:50%}.ladda-button.slide-up{overflow:hidden}.ladda-button.slide-up .label{position:relative}.ladda-button.slide-up .spinner{left:50%;margin-left:-16px;margin-top:1em}.ladda-button.slide-up[data-loading=true] .label{opacity:0;top:-1em}.ladda-button.slide-up[data-loading=true] .spinner{opacity:1;margin-top:-16px}.ladda-button.slide-down{overflow:hidden}.ladda-button.slide-down .label{position:relative}.ladda-button.slide-down .spinner{left:50%;margin-left:-16px;margin-top:-2em}.ladda-button.slide-down[data-loading=true] .label{opacity:0;top:1em}.ladda-button.slide-down[data-loading=true] .spinner{opacity:1;margin-top:-16px}.ladda-button.zoom-out{overflow:hidden}.ladda-button.zoom-out .spinner{left:50%;margin-left:-16px;-webkit-transform:scale(2.5);-moz-transform:scale(2.5);-ms-transform:scale(2.5);transform:scale(2.5)}.ladda-button.zoom-out .label{position:relative;display:inline-block}.ladda-button.zoom-out[data-loading=true] .label{opacity:0;-webkit-transform:scale(0.5);-moz-transform:scale(0.5);-ms-transform:scale(0.5);transform:scale(0.5)}.ladda-button.zoom-out[data-loading=true] .spinner{opacity:1;-webkit-transform:none;-moz-transform:none;-ms-transform:none;transform:none}.ladda-button.zoom-in{overflow:hidden}.ladda-button.zoom-in .spinner{left:50%;margin-left:-16px;-webkit-transform:scale(0.2);-moz-transform:scale(0.2);-ms-transform:scale(0.2);transform:scale(0.2)}.ladda-button.zoom-in .label{position:relative;display:inline-block}.ladda-button.zoom-in[data-loading=true] .label{opacity:0;-webkit-transform:scale(2.2);-moz-transform:scale(2.2);-ms-transform:scale(2.2);transform:scale(2.2)}.ladda-button.zoom-in[data-loading=true] .spinner{opacity:1;-webkit-transform:none;-moz-transform:none;-ms-transform:none;transform:none}.ladda-button.contract{overflow:hidden;width:100px}.ladda-button.contract .spinner{left:50%;margin-left:-16px}.ladda-button.contract[data-loading=true]{border-radius:50%;width:52px}.ladda-button.contract[data-loading=true] .label{opacity:0}.ladda-button.contract[data-loading=true] .spinner{opacity:1}.ladda-button.contract-overlay{overflow:hidden;width:100px;box-shadow:0 0 0 3000px transparent}.ladda-button.contract-overlay .spinner{left:50%;margin-left:-16px}.ladda-button.contract-overlay[data-loading=true]{border-radius:50%;width:52px;box-shadow:0 0 0 3000px rgba(0,0,0,.8)}.ladda-button.contract-overlay[data-loading=true] .label{opacity:0}.ladda-button.contract-overlay[data-loading=true] .spinner{opacity:1}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg)}}@-moz-keyframes spin{0%{-moz-transform:rotate(0deg)}100%{-moz-transform:rotate(360deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0deg)}100%{-o-transform:rotate(360deg)}}@-ms-keyframes spin{0%{-ms-transform:rotate(0deg)}100%{-ms-transform:rotate(360deg)}}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}html{height:100%}body{font-family:'Open Sans';font-weight:400;margin:0;color:#212121;background:#fff;font-size:13px;line-height:1.3;-webkit-font-smoothing:antialiased;height:100%;position:relative}[ng\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak{display:none}#container{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;padding-top:55px;position:relative;min-width:100%;min-height:100%;display:flex;flex-direction:row-reverse;justify-content:space-between}#header{background:#212121;position:fixed;height:55px;top:0;left:0;right:0;z-index:9;color:#fff;font-size:15px;line-height:55px;text-align:center;box-shadow:1px 1px 4px rgba(0,0,0,.5)}#header .brand{display:inline-block;font-family:Orbitron;font-size:26px;line-height:55px;text-decoration:none;text-transform:uppercase;color:#CCC}#header .burger{position:absolute;top:0;left:31px;height:55px;font-size:22px;color:#CCC}#header .burger i.fa{line-height:55px}#header .login,#header .user{position:absolute;right:0;top:0;bottom:0;white-space:nowrap;margin-right:20px;display:inline-block}#header .login a,#header .user a{color:#CCC;text-decoration:none;text-transform:uppercase;line-height:55px;font-size:15px}#header .login a img,#header .user a img{border-radius:50%;float:right;width:32px;height:32px;margin-top:10px;margin-left:20px}#body{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;display:flex;min-width:100%;flex-direction:row-reverse;justify-content:space-between}#body article{width:100%}#drawer{visibility:hidden;position:fixed;z-index:10;left:0;top:55px;bottom:0;width:255px;background:#363636;-webkit-transition:all .2s;-moz-transition:all .2s;transition:all .2s;-webkit-transform:translate3d(-100%,0,0);-moz-transform:translate3d(0,0,0);transform:translate3d(-100%,0,0)}#drawer ul{margin-top:20px}#drawer ul a{color:#CCC;text-decoration:none;padding:10px 0 10px 30px;display:block;font-size:14px}#drawer ul a i{margin-right:10px;font-size:16px;opacity:.3;min-width:16px;display:inline-block}#drawer ul span.divider{display:block;height:1px;border-top:1px solid rgba(255,255,255,.1);margin-top:15px;margin-bottom:15px}#drawer .signout{position:absolute;bottom:20px;right:30px;color:#CCC;font-size:16px;text-transform:uppercase;text-decoration:none}#drawer .signout i{margin-left:20px}#drawer-checkbox{position:fixed;top:7px;left:10px;width:45px;height:40px;display:block;z-index:9999;opacity:0;background:0 0;border:none;cursor:pointer}#drawer-checkbox:checked~#drawer{visibility:visible;-webkit-transform:translate3d(0,0,0);-moz-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}#drawer-checkbox:checked~#drawer{visibility:visible;-webkit-transform:translate3d(0,0,0);-moz-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}#drawer-checkbox:checked~#header .fa-bars:before{content:"\f00d";color:#999}nav{padding-left:30px;background:#FFF;min-height:77px;max-height:77px;line-height:77px;font-family:'Open Sans';font-size:22px;white-space:nowrap;color:rgba(0,0,0,.7);border-bottom:1px solid #eee;position:relative;z-index:2}nav a{text-decoration:none;color:rgba(0,0,0,.7)}nav a:last-child{color:#000}nav a span.fa{margin-right:20px}nav div.options{float:right;margin-right:20px}nav div.options .pure-button{color:#FFF;text-transform:lowercase;font-size:14px;background:#f5f5f5;padding:10px 30px;color:rgba(0,0,0,.5)}nav div.options .pure-button i{margin-right:10px;margin-left:-10px}@supports (position:sticky){nav{position:sticky;top:55px}}@supports (position:-moz-sticky){nav{position:-moz-sticky;top:55px}}@supports (position:-webkit-sticky){nav{position:-webkit-sticky;top:55px}}.cards .card{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;padding-right:20px;padding-bottom:20px;padding-left:20px;text-decoration:none;position:relative;color:#212121;font-family:'Open Sans'}.cards .card[data-status=Success] em{border-top:5px solid #7cb342}.cards .card[data-status=Killed] em,.cards .card[data-status=Failure] em,.cards .card[data-status=Error] em{border-top:5px solid #f44336}.cards .card[data-status=Killed] .l-box,.cards .card[data-status=Failure] .l-box,.cards .card[data-status=Error] .l-box{border:1px solid #f44336}.cards .card[data-status=Killed] .l-box:hover,.cards .card[data-status=Failure] .l-box:hover,.cards .card[data-status=Error] .l-box:hover{border:1px solid #212121}.cards .card[data-status=Killed]:after,.cards .card[data-status=Failure]:after,.cards .card[data-status=Error]:after{font-family:FontAwesome;font-size:16px;position:absolute;right:12px;top:-8px;content:"\f111";color:#f44336;min-width:16px;text-align:center}.cards .card .l-box{background:#FFF;border:1px solid #DDD;position:relative;padding:30px 20px;height:200px;-webkit-transition:.4s border linear;-moz-transition:.4s border linear;-ms-transition:.4s border linear;-o-transition:.4s border linear;transition:.4s border linear;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box}.cards .card .l-box:hover{border:1px solid #212121}.cards .card .l-box em{position:absolute;bottom:20px;right:20px;left:20px;height:30px;line-height:30px;vertical-align:middle;text-align:right;padding-right:45px;padding-top:20px;font-size:14px;color:#666}.cards .card .l-box img{position:absolute;right:20px;bottom:20px;border-radius:50%;width:30px;height:30px}.cards .card .l-box .timeago{position:absolute;bottom:85px;left:25px;right:25px;text-align:right;font-size:14px;color:#849299;display:none}.cards .card h2{font-size:18px;font-weight:400;min-height:52px;max-height:52px;height:52px;text-align:center;vertical-align:middle;line-height:26px;color:#212121;text-overflow:ellipsis;-webkit-line-clamp:2;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.cards .card h2 .separator{margin:0 0}.cards .card.card-inactive .l-box{position:relative;box-shadow:none;background:#4ab1ce;color:#FFF;height:180px;border-color:#4ab1ce}.cards .card.card-inactive .l-box:hover{background:#3197b4}.cards .card.card-inactive .l-box:hover:before{background:#3197b4}.cards .card.card-inactive h2{padding-top:10px;color:#FFF}.cards .card.card-inactive em{position:absolute;border-top:1px solid rgba(255,255,255,.5);bottom:15px;font-size:13px;left:25px;right:25px;line-height:1.3;padding:0;padding-top:20px;text-align:center;display:block;height:30px;text-transform:uppercase;color:#FFF}.cards .card.card-browse-inactive,.cards .card.card-browse{text-align:center;color:#4ab1ce;font-size:16px;font-weight:700;text-transform:uppercase}.cards .card.card-browse-inactive .l-box,.cards .card.card-browse .l-box{padding-top:75px;background:#FFF;height:180px}.cards .card.card-browse-inactive .l-box{box-shadow:none}.cards .progressContainer{height:5px;background-color:#f44336;position:absolute;bottom:65px;left:20px;right:20px}.cards .progressContainer .activeProgress,.cards .progressContainer .secondaryProgress{position:absolute;top:0;left:0;bottom:0}.cards .progressContainer .activeProgress{background-color:#7cb342}.cards .progressContainer .secondaryProgress{background-color:#7cb342}#commitpage{max-width:1180px;margin:0 auto;margin-bottom:50px;margin-top:70px}#commitpage section{margin-top:30px}#commitpage section .commits{border:1px solid #DDD;border-bottom:0 solid #DDD;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box}#commitpage section .commits a{padding:20px 45px;display:block;border-bottom:1px solid #dadcdd;color:#212121;text-decoration:none;position:relative;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box}#commitpage section .commits a h2{font-family:'Open Sans';font-weight:700;font-size:16px;margin-bottom:5px}#commitpage section .commits a img{border-radius:50%;margin-right:10px;float:left;display:none}#commitpage section .commits a p{color:#363636;line-height:22px;vertical-align:middle}#commitpage section .commits a[data-status]:before{background:0 0;width:7px;min-width:7px;max-width:7px;position:absolute;left:-1px;top:0;bottom:0;text-align:left;color:#fff;font-size:20px;line-height:50px;font-family:'Open Sans';padding-left:2px;overflow:hidden;content:" "}#commitpage section .commits a[data-result=Killed],#commitpage section .commits a[data-status=Error],#commitpage section .commits a[data-status=Failure]{background:#fff9f5}#commitpage section .commits a[data-result=Killed]:before,#commitpage section .commits a[data-status=Error]:before,#commitpage section .commits a[data-status=Failure]:before{background:#f44336;content:"!"}#commitpage section .commits a[data-status=Success]:before{background:#7cb342}#commitpage .date span{display:inline-block;text-align:right;font-size:14px;width:100%;padding-right:30px;margin-top:15px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box}#setuppage .pure-g,#loginpage .pure-g{padding:30px;border:1px solid #DDD;max-width:400px;margin:0 auto;margin-top:50px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box}#setuppage .pure-g a,#loginpage .pure-g a{display:block;background:#45494b;color:#fff;padding:14px 20px;font-size:15px;border-radius:5px;text-decoration:none}#setuppage .pure-g a:hover,#loginpage .pure-g a:hover{background:#212121}#setuppage .pure-g [class*=fa-],#loginpage .pure-g [class*=fa-]{float:left;font-size:20px;position:relative;top:-3px;left:-3px;padding-right:10px;min-width:27px;min-height:20px}#setuppage .pure-g .pure-u-1 a,#loginpage .pure-g .pure-u-1 a{margin-bottom:10px}#setuppage .pure-g .pure-u-1:last-child a,#loginpage .pure-g .pure-u-1:last-child a{margin-bottom:0}#setuppage form.pure-g input[type=text],#loginpage form.pure-g input[type=text],#setuppage form.pure-g input[type=password],#loginpage form.pure-g input[type=password]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;width:100%;padding:8px;font-size:14px;margin-bottom:15px;border:1px solid #DDD}#setuppage form.pure-g input[type=submit],#loginpage form.pure-g input[type=submit]{display:block;background:#45494b;color:#fff;padding:14px 20px;font-size:15px;border-radius:5px;text-decoration:none;width:100%;border:none}#setuppage form.pure-g input[type=submit]:hover,#loginpage form.pure-g input[type=submit]:hover{background:#212121}#setuppage2{margin-bottom:50px}#setuppage2 section{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box}#setuppage2 section .pure-g{padding:30px;border:1px solid #DDD;max-width:400px;margin:0 auto;margin-top:50px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box}#setuppage2 section label{display:inline-block}#setuppage2 section input[type=text]{margin-top:5px;margin-bottom:10px;box-shadow:none;width:100%}#setuppage2 section .pure-button-primary{color:#FFF;background:#4ab1ce;padding:10px 20px;margin-top:20px;width:100%}#setuppage2 section .tip h2{font-size:16px;margin-bottom:20px}#setuppage2 section .tip dd{font-weight:700;color:#666;margin-top:15px;margin-bottom:5px}#setuppage2 section .tip dt{padding:.5em .6em;display:inline-block;border:1px solid #ccc;border-radius:4px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;width:100%}#syncpage{width:100%}#syncpage section{padding:40px 0 20px 0}#syncpage section h1{text-align:center;font-size:24px;margin-bottom:20px}#syncpage section h1 i{font-size:32px;margin-top:20px}#homepage{width:100%;background:#fafafa}#homepage section{padding:40px 0 20px 0}#homepage section h1{text-align:center;font-size:24px;margin-bottom:20px}#homepage section h1 i{font-size:32px;margin-top:20px}#homepage section div{max-width:1180px;margin:0 auto}#homepage section:nth-child(2){background:#FFF;padding:40px 0 20px 0}#homepage section:nth-child(3){border-bottom:0 solid #EEE}#homepage section .card[data-status=Killed] .l-box,#homepage section .card[data-status=Failure] .l-box,#homepage section .card[data-status=Error] .l-box{border:1px solid #f44336}#homepage section .card[data-status=Killed] .l-box:hover,#homepage section .card[data-status=Failure] .l-box:hover,#homepage section .card[data-status=Error] .l-box:hover{border:1px solid #212121}#repospage{width:100%}#repospage section{border-bottom:1px solid #eee;max-width:768px;margin:0 auto;margin-top:30px;margin-bottom:30px}#repospage section .search{margin-bottom:25px}#repospage section .search input[type=text],#repospage section .search input[type=search]{-webkit-transition:.4s border linear;-moz-transition:.4s border linear;-ms-transition:.4s border linear;-o-transition:.4s border linear;transition:.4s border linear;border:1px solid #ccc;border-radius:0;box-shadow:none;padding:12px}#repospage section .search input[type=text]:focus,#repospage section .search input[type=search]:focus{border-color:#129FEA}#repospage section .repo{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;text-decoration:none}#repospage section .repo:last-child>div{border-bottom:1px solid #fff}#repospage section .repo>div{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;border:1px solid #eee;border-bottom:1px solid #fff;-webkit-transition:.4s border linear;-moz-transition:.4s border linear;-ms-transition:.4s border linear;-o-transition:.4s border linear;transition:.4s border linear}#repospage section .repo>div>div{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;padding:20px 25px}#repospage section .repo>div>div:last-child div{text-align:right}#repospage section .repo>div:hover{border:1px solid #212121}#repospage section .repo h4{font-size:20px;margin-bottom:2px;color:#212121}#repospage section .repo span{color:#666;font-size:14px}#repospage section .repo i{color:#DDD;font-size:22px;margin-left:20px;margin-top:15px}#repospage section .repo i.fa-check-circle-o{color:#7cb342}#userspage{width:100%}#userspage section{border-bottom:1px solid #eee;max-width:768px;margin:0 auto;margin-top:30px;margin-bottom:30px}#userspage section .search{margin-bottom:25px}#userspage section .search input[type=text],#userspage section .search input[type=search]{-webkit-transition:.4s border linear;-moz-transition:.4s border linear;-ms-transition:.4s border linear;-o-transition:.4s border linear;transition:.4s border linear;border:1px solid #ccc;border-radius:0;box-shadow:none;padding:12px}#userspage section .search input[type=text]:focus,#userspage section .search input[type=search]:focus{border-color:#129FEA}#userspage section .user{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;text-decoration:none}#userspage section .user:last-child>div{border-bottom:1px solid #fff}#userspage section .user>div{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;border:1px solid #eee;border-bottom:1px solid #fff;-webkit-transition:.4s border linear;-moz-transition:.4s border linear;-ms-transition:.4s border linear;-o-transition:.4s border linear;transition:.4s border linear}#userspage section .user>div>div{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;padding:20px 25px;padding-right:0}#userspage section .user>div:hover{border:1px solid #212121}#userspage section .user img{border-radius:50%;width:48px;height:48px}#userspage section .user h4{font-size:20px;margin-bottom:2px;color:#212121}#userspage section .user h4 small{font-size:16px;color:#666;margin-left:5px}#userspage section .user span{color:#666;font-size:14px}#repoconfpage{width:100%}#repoconfpage section{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;border:1px solid #eee;max-width:768px;margin:0 auto;margin-top:30px;margin-bottom:30px;padding:20px}#repoconfpage section h2{font-size:16px;margin-bottom:15px}#repoconfpage section .markdown,#repoconfpage section .params,#repoconfpage section .key{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;min-height:50px;margin-top:10px;font-family:'Droid Sans Mono';border:1px solid #eee;padding:20px;width:100%;max-width:100%;color:#666}#repoconfpage section .markdown:focus,#repoconfpage section .params:focus,#repoconfpage section .key:focus{border-color:#129FEA;outline:0}#repoconfpage section .pure-button-primary{color:#FFF;background:#4ab1ce;padding:10px 20px;margin-top:20px}#repoconfpage section select,#repoconfpage section input[type=number]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;padding:8px;font-size:14px;margin-bottom:15px;border:1px solid #DDD}#repoconfpage section span.seconds{color:#212121;margin-left:10px}#repoconfpage section span.minutes{color:#212121}#repoconfpage section span.minutes:before{content:'('}#repoconfpage section span.minutes:after{content:')'}#accountpage{width:100%}#accountpage section{position:relative;max-width:768px;margin:0 auto;margin-top:30px;border:1px solid #eee}#accountpage section.profile>div:first-child{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;padding:20px;text-align:center}#accountpage section.profile>div:last-child{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;padding:20px}#accountpage section.profile .fullname{font-size:14px;margin-bottom:2px;color:#666;display:block}#accountpage section.profile .email{font-size:14px;color:#666;display:block}#accountpage section.token>div:first-child div{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;text-align:center;padding:20px;color:#666;font-size:16px;line-height:22px}#accountpage section.token>div:first-child i{margin-right:7px}#accountpage section.token>div:last-child{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;padding:20px;color:#666;line-height:22px;font-size:14px;word-break:break-all}#accountpage section h4{margin:10px 0;font-size:22px}#accountpage section h4 small{opacity:.6;font-size:16px;margin-left:10px}#accountpage section img{width:64px;height:64px;border-radius:50%}#accountpage section .notifications{position:absolute;top:0;right:0;margin:20px}#accountpage section .button-error{color:#FFF;background:#ca3c3c;padding:10px 20px;float:right}#accountpage section .pure-button-primary{color:#FFF;background:#4ab1ce;padding:10px 20px;margin-top:10px}#repopage{width:100%;background:#fafafa}#repopage section{padding:40px 0 20px 0}#repopage section>div{max-width:1180px;margin:0 auto}#repopage section:nth-child(even){background:#FFF}#repopage section:first-child{background:#FFF}#repopage section .card[data-status=Success]:nth-child(2) .l-box{border-color:#7cb342}#repopage section .card[data-status=Success]:nth-child(2) .l-box:hover{border:1px solid #212121}#repopage section .card[data-status=Killed]:nth-child(2) .l-box,#repopage section .card[data-status=Failure]:nth-child(2) .l-box,#repopage section .card[data-status=Error]:nth-child(2) .l-box{border-color:#f44336}#repopage section .card[data-status=Killed]:nth-child(2) .l-box:hover,#repopage section .card[data-status=Failure]:nth-child(2) .l-box:hover,#repopage section .card[data-status=Error]:nth-child(2) .l-box:hover{border:1px solid #212121}#repopage section .card[data-status=Started] em:before,#repopage section .card[data-status=Pending] em:before{-webkit-animation:progress 1s linear infinite;-moz-animation:progress 1s linear infinite;animation:progress 1s linear infinite;position:absolute;content:' ';height:5px;top:-5px;left:0;right:0;margin:0;background:#ffb300;background-image:-webkit-linear-gradient(-45deg,rgba(255,255,255,.55) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.55) 50%,rgba(255,255,255,.55) 75%,transparent 75%,transparent);background-image:linear-gradient(-45deg,rgba(255,255,255,.55) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.55) 50%,rgba(255,255,255,.55) 75%,transparent 75%,transparent);background-repeat:repeat-x;background-size:30px 30px}#repopage section .l-box em{line-height:19px;bottom:25px}#repopage section .l-box:after{font-family:FontAwesome;content:"\f104";content:"\f0d9";position:absolute;right:-20px;width:20px;text-align:center;color:rgba(0,0,0,.1);font-size:22px}#repopage section .card:last-child .l-box:after{content:''}#repopage section.nobuilds,#repopage section.inactive{text-align:center;padding-bottom:50px}#repopage section.nobuilds h1,#repopage section.inactive h1{font-size:26px;color:#212121}#repopage section.nobuilds p,#repopage section.inactive p{font-size:16px;color:#666}#repopage section.nobuilds i,#repopage section.inactive i{font-size:32px;margin-top:20px;margin-bottom:20px}#repopage section.nobuilds i.fa-file-code-o,#repopage section.inactive i.fa-file-code-o{font-size:42px;margin-top:30px}#repopage section.nobuilds .pure-button-primary,#repopage section.inactive .pure-button-primary{font-size:14px;text-transform:uppercase;background:#4ab1ce;padding:10px 30px}@-webkit-keyframes progress{to{background-position:30px 0}}@-moz-keyframes progress{to{background-position:30px 0}}@keyframes progress{to{background-position:30px 0}}#sidebar{width:240px;min-width:240px;position:relative;display:block;z-index:5;padding:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box}#sidebar h1{font-size:28px;font-weight:300}#sidebar h2{font-size:22px;font-weight:300;margin-bottom:20px}#sidebar dl{padding-top:23px;border-top:1px solid #ddd;margin-top:5px}#sidebar dl:first-child{padding-top:0;border-top:none;margin-top:0}#sidebar dl dt{font-size:12px;color:#849299;text-transform:uppercase;padding:3px 0}#sidebar dl dd{font-size:14px;padding:3px 0 20px}#sidebar dl a{text-transform:none}#sidebar dl small{font-size:12px}#sidebar dl .large{font-size:18px;padding-bottom:5px}#sidebar dl .large a{color:#212121}#sidebar dl .time{float:right;margin-left:8px}#sidebar dl .photo{margin-right:4px}#sidebar dl .negative{color:#f44336}#sidebar dl .photoline{display:inline-block;position:relative;top:-10px;font-weight:700}#sidebar dl .small{padding-bottom:5px;font-weight:700;font-size:12px}#sidebar .status{border:1px solid transparent;display:block;text-align:center;padding:5px 20px;border-radius:50px;text-transform:uppercase;margin:0 -5px 10px;font-weight:700}#sidebar .status:before{float:left;margin-left:-5px}#sidebar .status.status_ok{color:#7cb342;border-color:#7cb342}#sidebar .status.status_ok:before{content:"\f00c";font-family:FontAwesome}#sidebar .status.status_error{color:#f44336;border-color:#f44336}#sidebar .status.status_error:before{content:"!"}#sidebar .result{background:#4ab1ce;background:#7cb342;color:#fff;margin:-30px -30px -6px;padding:30px;position:relative}#sidebar .result .status{color:#fff;background:rgba(255,255,255,.2)}#sidebar .result .status:before{content:"\f00c";font-family:FontAwesome}#sidebar .result dl dd{padding:7px 0}#sidebar .result dl dd strong{font-size:16px}#sidebar .result[data-result=Killed],#sidebar .result[data-result=Failure],#sidebar .result[data-result=Error]{background:#f44336}#sidebar .result[data-result=Killed] .status:before,#sidebar .result[data-result=Failure] .status:before,#sidebar .result[data-result=Error] .status:before{content:"!"}#sidebar .result[data-result=Pending],#sidebar .result[data-result=Started]{background:#ffb300}#sidebar .result[data-result=Pending] .status:before,#sidebar .result[data-result=Started] .status:before{content:"\f021";-webkit-animation:spin 2s infinite linear;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;animation:spin 2s infinite linear}#main{flex-grow:2}#main.output{position:relative;background:#212121}#main.output pre{margin:0 auto;padding:30px;color:#FFF;font-family:'Droid Sans Mono';font-size:13px;white-space:pre-wrap;overflow:hidden;line-height:18px}#main.output #follow button{position:fixed;right:280px;bottom:21px;z-index:100;border-radius:7px;background-color:rgba(238,238,238,.2);padding:0 1em;cursor:pointer;font-family:'Open Sans';border:none;color:#fff}#main.output[data-result=Pending]:after,#main.output[data-result=Started]:after{position:absolute;right:20px;bottom:20px;color:#9e9e9e;font-size:18px;font-family:FontAwesome;content:"\f021";-webkit-animation:spin 2s infinite linear;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;animation:spin 2s infinite linear}.pure-form.pure-form-stacked label{font-size:14px;color:#666;margin-top:20px;margin-bottom:5px}.pure-form.pure-form-stacked label:first-child{margin-top:0}.pure-form.pure-form-stacked input[type=text],.pure-form.pure-form-stacked select{min-width:300px;box-shadow:none;padding:10px 10px;border:1px solid #ccc;border-radius:0}.toggle{margin-bottom:10px}.toggle:nth-child(2){margin-top:40px}.toggle span{line-height:32px;vertical-align:middle;display:inline-block;margin-left:10px}.toggle input[type=checkbox]{max-height:0;max-width:0;opacity:0}.toggle input[type=checkbox]+label{display:inline-block;vertical-align:middle;position:relative;box-shadow:inset 0 0 0 1px #d5d5d5;text-indent:-5000px;height:30px;width:50px;border-radius:15px}.toggle input[type=checkbox]+label:before{content:"";position:absolute;display:block;height:30px;width:30px;top:0;left:0;border-radius:15px;background:rgba(19,191,17,0);-webkit-transition:.25s ease-in-out;-moz-transition:.25s ease-in-out;-ms-transition:.25s ease-in-out;-o-transition:.25s ease-in-out;transition:.25s ease-in-out}.toggle input[type=checkbox]+label:after{content:"";position:absolute;display:block;height:30px;width:30px;top:0;left:0;border-radius:15px;background:#fff;box-shadow:inset 0 0 0 1px rgba(0,0,0,.2),0 2px 4px rgba(0,0,0,.2);-webkit-transition:.25s ease-in-out;-moz-transition:.25s ease-in-out;-ms-transition:.25s ease-in-out;-o-transition:.25s ease-in-out;transition:.25s ease-in-out}.toggle input[type=checkbox]:checked+label:before{width:50px;background:#4ab1ce}.toggle input[type=checkbox]:checked+label:after{left:20px;box-shadow:inset 0 0 0 1px #4ab1ce,0 2px 4px rgba(0,0,0,.2)}.toast{position:fixed;bottom:50px;left:20%;right:20%;background:#363636;border-radius:3px;z-index:999;color:#FFF;padding:15px 20px;font-size:14px;box-shadow:2px 2px 2px rgba(0,0,0,.2)}.toast a,.toast a:visited,.toast a:hover,.toast a:active{color:#FFF}.toast button{float:right;background:0 0;border:none;color:#EEFF41;text-transform:uppercase;margin-left:10px}@media screen and (min-width:1180px){#repopage h2{padding-left:0}.cards .card{padding-left:0}}@media screen and (max-width:35.5em){.cards .card .l-box:after{display:none!IMPORTANT}header .username{display:none}#repopage h2{padding-left:20px}#userspage form,#repospage form{display:none}#userspage section,#repospage section{margin:0}#userspage section .user:nth-child(2)>.pure-g,#repospage section .user:nth-child(2)>.pure-g,#userspage section .repo:nth-child(2)>.pure-g,#repospage section .repo:nth-child(2)>.pure-g{border-top:1px solid #FFF}#userspage section .user:nth-child(2):hover>.pure-g,#repospage section .user:nth-child(2):hover>.pure-g,#userspage section .repo:nth-child(2):hover>.pure-g,#repospage section .repo:nth-child(2):hover>.pure-g{border-top:1px solid #212121}#userspage section .user i,#repospage section .user i,#userspage section .repo i,#repospage section .repo i{margin-left:0}#accountpage .token span{display:none}nav div.options .pure-button i{margin:0}nav div.options .pure-button span{display:none}} \ No newline at end of file From 50e368c24a01e1793ff607c3bba6f5cd9d190347 Mon Sep 17 00:00:00 2001 From: Eichin David Date: Tue, 28 Oct 2014 14:13:23 +0100 Subject: [PATCH 10/11] refactoring commit controller fixed bug where include of pr template raised angularjs error --- server/app/index.html | 1 + server/app/scripts/app.js | 90 ---------------------- server/app/scripts/controllers/commit.js | 95 ++++++++++++++++++++++++ server/app/scripts/services/stdout.js | 34 +++++---- server/app/views/commit.html | 4 +- 5 files changed, 116 insertions(+), 108 deletions(-) create mode 100644 server/app/scripts/controllers/commit.js diff --git a/server/app/index.html b/server/app/index.html index fe60da6bc..3b359e5d1 100644 --- a/server/app/index.html +++ b/server/app/index.html @@ -37,6 +37,7 @@ + diff --git a/server/app/scripts/app.js b/server/app/scripts/app.js index 263821c2f..322dd510e 100644 --- a/server/app/scripts/app.js +++ b/server/app/scripts/app.js @@ -224,93 +224,3 @@ app.controller("AccountReposController", function($scope, $http, user) { return $scope.remote == "" || $scope.remote == entry.remote; }; }); - - -app.controller("CommitController", function($scope, $http, $route, $routeParams, stdout, feed) { - - var remote = $routeParams.remote; - var owner = $routeParams.owner; - var name = $routeParams.name; - var branch = $routeParams.branch; - var commit = $routeParams.commit; - $scope.console=''; - - var handleOutput = function(id, clearConsole) { - var lineFormatter = new Drone.LineFormatter(); - var el = document.querySelector('#output'); - if(clearConsole === true) { - el.innerHTML = ''; - } - stdout.subscribe(id, function(out){ - angular.element(el).append(lineFormatter.format(out)); - if ($scope.following) { - window.scrollTo(0, document.body.scrollHeight); - } - }); - } - - feed.subscribe(function(item) { - if (item.commit.sha == commit && - item.commit.branch == branch) { - if(item.commit.status == "Started") { - handleOutput(item.commit.id, true); - } - $scope.commit = item.commit; - $scope.$apply(); - - } else { - // we trigger an toast notification so the - // user is aware another build started - - } - }); - - // load the repo meta-data - $http({method: 'GET', url: '/api/repos/'+remote+'/'+owner+"/"+name}). - success(function(data, status, headers, config) { - $scope.repo = data; - }). - error(function(data, status, headers, config) { - console.log(data); - }); - - // load the repo commit data - $http({method: 'GET', url: '/api/repos/'+remote+'/'+owner+"/"+name+"/branches/"+branch+"/commits/"+commit}). - success(function(data, status, headers, config) { - $scope.commit = data; - - if (data.status!='Started' && data.status!='Pending') { - $http({method: 'GET', url: '/api/repos/'+remote+'/'+owner+"/"+name+"/branches/"+branch+"/commits/"+commit+"/console"}). - success(function(data, status, headers, config) { - var lineFormatter = new Drone.LineFormatter(); - var el = document.querySelector('#output'); - angular.element(el).append(lineFormatter.format(data)); - }). - error(function(data, status, headers, config) { - console.log(data); - }); - return; - } - - handleOutput(data.id, false); - - }). - error(function(data, status, headers, config) { - console.log(data); - }); - - $scope.following = false; - $scope.follow = function() { - $scope.following = true; - window.scrollTo(0, document.body.scrollHeight); - } - $scope.unfollow = function() { - $scope.following = false; - } - - $scope.rebuildCommit = function() { - $http({method: 'POST', url: '/api/repos/'+remote+'/'+owner+'/'+name+'/'+'branches/'+branch+'/'+'commits/'+commit+'?action=rebuild' }); - } - - -}); diff --git a/server/app/scripts/controllers/commit.js b/server/app/scripts/controllers/commit.js new file mode 100644 index 000000000..1db3594a0 --- /dev/null +++ b/server/app/scripts/controllers/commit.js @@ -0,0 +1,95 @@ +/*global angular, Drone, console */ +angular.module('app').controller("CommitController", function ($scope, $http, $route, $routeParams, stdout, feed) { + 'use strict'; + + var remote = $routeParams.remote, + owner = $routeParams.owner, + name = $routeParams.name, + branch = $routeParams.branch, + commit = $routeParams.commit, + // Create lineFormatter and outputElement since we need them anyway. + lineFormatter = new Drone.LineFormatter(), + outputElement = angular.element(document.querySelector('#output')); + + var connectRemoteConsole = function (id) { + // Clear console output if connecting to new remote console (rebuild) + if (!outputElement.html() !== 0) { + outputElement.empty(); + } + // Subscribe to stdout of the remote build + stdout.subscribe(id, function (out) { + // Append new output to console + outputElement.append(lineFormatter.format(out)); + // Scroll if following + if ($scope.following) { + window.scrollTo(0, document.body.scrollHeight); + } + }); + }; + + // Subscribe to feed so we can update gui if changes to the commit happen. (Build finished, Rebuild triggered, change from Pending to Started) + feed.subscribe(function (item) { + // If event is part of the active commit currently showing. + if (item.commit.sha === commit && + item.commit.branch === branch) { + // If new status is Started, connect to remote console to get live output + if (item.commit.status === "Started") { + connectRemoteConsole(item.commit.id); + } + $scope.commit = item.commit; + $scope.$apply(); + + } else { + // we trigger an toast notification so the + // user is aware another build started + + } + }); + + // Load the repo meta-data + $http({method: 'GET', url: '/api/repos/' + remote + '/' + owner + "/" + name}). + success(function (data, status, headers, config) { + $scope.repo = data; + }). + error(function (data, status, headers, config) { + console.log(data); + }); + + // Load the repo commit data + $http({method: 'GET', url: '/api/repos/' + remote + '/' + owner + "/" + name + "/branches/" + branch + "/commits/" + commit}). + success(function (data, status, headers, config) { + $scope.commit = data; + + // If build has already finished, load console output from database + if (data.status !== 'Started' && data.status !== 'Pending') { + $http({method: 'GET', url: '/api/repos/' + remote + '/' + owner + "/" + name + "/branches/" + branch + "/commits/" + commit + "/console"}). + success(function (data, status, headers, config) { + outputElement.append(lineFormatter.format(data)); + }). + error(function (data, status, headers, config) { + console.log(data); + }); + return; + // If build is currently running, connect to remote console; + } else if (data.status === 'Started') { + connectRemoteConsole(data.id); + } + + }). + error(function (data, status, headers, config) { + console.log(data); + }); + + $scope.following = false; + $scope.follow = function () { + $scope.following = true; + window.scrollTo(0, document.body.scrollHeight); + }; + $scope.unfollow = function () { + $scope.following = false; + }; + + $scope.rebuildCommit = function () { + $http({method: 'POST', url: '/api/repos/' + remote + '/' + owner + '/' + name + '/branches/' + branch + '/commits/' + commit + '?action=rebuild' }); + }; +}); diff --git a/server/app/scripts/services/stdout.js b/server/app/scripts/services/stdout.js index 894e26165..3c9d0f327 100644 --- a/server/app/scripts/services/stdout.js +++ b/server/app/scripts/services/stdout.js @@ -1,32 +1,34 @@ -'use strict'; +/*global angular, WebSocket, localStorage, console */ +angular.module('app').service('stdout', ['$window', function ($window) { + 'use strict'; + + var callback, + websocket, + token = localStorage.getItem('access_token'); -angular.module('app').service('stdout', ['$window', function($window) { - var callback = undefined; - var websocket = undefined; - var token = localStorage.getItem('access_token'); - - this.subscribe = function(path, _callback) { + this.subscribe = function (path, _callback) { callback = _callback; - var proto = ($window.location.protocol == 'https:' ? 'wss' : 'ws'); - var route = [proto, "://", $window.location.host, '/api/stream/stdout/', path, '?access_token=', token].join(''); + var proto = ($window.location.protocol === 'https:' ? 'wss' : 'ws'), + route = [proto, "://", $window.location.host, '/api/stream/stdout/', path, '?access_token=', token].join(''); websocket = new WebSocket(route); - websocket.onmessage = function(event) { - if (callback != undefined) { + websocket.onmessage = function (event) { + if (callback !== undefined) { callback(event.data); } }; - websocket.onclose = function(event) { - console.log('websocket closed at '+path); + websocket.onclose = function (event) { + console.log('websocket closed at ' + path); }; }; - this.unsubscribe = function() { + this.unsubscribe = function () { callback = undefined; - if (websocket != undefined) { - console.log('unsubscribing websocket at '+websocket.url); + if (websocket !== undefined) { + console.log('unsubscribing websocket at ' + websocket.url); websocket.close(); + websocket = undefined; } }; }]); diff --git a/server/app/views/commit.html b/server/app/views/commit.html index 1f398e6c0..2cfdfd80b 100644 --- a/server/app/views/commit.html +++ b/server/app/views/commit.html @@ -11,8 +11,8 @@

{{ commit.duration | toDuration}}

-
-
+
+
{{ commit.finished_at | fromNow }}
Started {{ commit.started_at | fromNow }}
Created {{ commit.created_at}}
From eef53530c1d010ecc2b98c52f02aff843ea4c26b Mon Sep 17 00:00:00 2001 From: Kirill Zaitsev Date: Tue, 28 Oct 2014 18:16:50 +0300 Subject: [PATCH 11/11] Allow sync manually, instead force sync at ever login --- server/app/scripts/app.js | 15 ++++++- server/app/scripts/controllers/home.js | 15 ++++++- server/app/scripts/controllers/sync.js | 10 ++++- server/app/views/home.html | 11 ++++++ server/app/views/repo_list.html | 12 ++++++ server/handler/login.go | 52 +++--------------------- server/handler/user.go | 36 +++++++++++++++++ server/router/router.go | 1 + server/sync/sync.go | 55 ++++++++++++++++++++++++++ 9 files changed, 156 insertions(+), 51 deletions(-) create mode 100644 server/sync/sync.go diff --git a/server/app/scripts/app.js b/server/app/scripts/app.js index 263821c2f..a8dbf30a9 100644 --- a/server/app/scripts/app.js +++ b/server/app/scripts/app.js @@ -198,10 +198,23 @@ app.run(['$location', '$rootScope', '$routeParams', 'feed', 'stdout', function($ -app.controller("AccountReposController", function($scope, $http, user) { +app.controller("AccountReposController", function($scope, $http, $location, user) { $scope.user = user; + $scope.syncUser = function() { + $http({method: 'POST', url: '/api/user/sync' }).success(function(data){ + $location.search('return_to', $location.$$path).path('/sync') + }).error(function(data, status){ + if (status == 409) { + $scope.msg = 'already' + } else { + $scope.msg = 'bad' + } + $scope.$apply(); + }); + } + // get the user details $http({method: 'GET', url: '/api/user/repos'}). success(function(data, status, headers, config) { diff --git a/server/app/scripts/controllers/home.js b/server/app/scripts/controllers/home.js index 18d612acf..953fd9704 100644 --- a/server/app/scripts/controllers/home.js +++ b/server/app/scripts/controllers/home.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('app').controller("HomeController", function($scope, $http, feed) { +angular.module('app').controller("HomeController", function($scope, $http, $location, feed) { feed.subscribe(function(item) { // todo toast notification @@ -14,6 +14,19 @@ angular.module('app').controller("HomeController", function($scope, $http, feed) console.log(data); }); + $scope.syncUser = function() { + $http({method: 'POST', url: '/api/user/sync' }).success(function(data){ + $location.search('return_to', $location.$$path).path('/sync') + }).error(function(data, status){ + if (status == 409) { + $scope.msg = 'already' + } else { + $scope.msg = 'bad' + } + $scope.$apply(); + }); + } + $http({method: 'GET', url: '/api/user/repos'}). success(function(data, status, headers, config) { $scope.repos = (typeof data==='string')?[]:data; diff --git a/server/app/scripts/controllers/sync.js b/server/app/scripts/controllers/sync.js index 909f79aee..9d3521f73 100644 --- a/server/app/scripts/controllers/sync.js +++ b/server/app/scripts/controllers/sync.js @@ -1,10 +1,16 @@ 'use strict'; -angular.module('app').controller("SyncController", function($scope, $http, $interval, $location, users) { +angular.module('app').controller("SyncController", function($scope, $http, $interval, $location, $routeParams, users) { + var return_to = $routeParams.return_to var stop = $interval(function() { // todo(bradrydzewski) We should poll the user to see if the // sync process is complete, using the user.syncing variable. $interval.cancel(stop); - $location.path("/"); + if (return_to != undefined) { + $location.$$search = {} + $location.path(return_to); + } else { + $location.path("/"); + } }, 5000); }); diff --git a/server/app/views/home.html b/server/app/views/home.html index 5e2940170..68c452656 100644 --- a/server/app/views/home.html +++ b/server/app/views/home.html @@ -1,7 +1,18 @@ +
+ sync already runned + bad response +
+
diff --git a/server/app/views/repo_list.html b/server/app/views/repo_list.html index 3a486b6b6..4fda16c8f 100644 --- a/server/app/views/repo_list.html +++ b/server/app/views/repo_list.html @@ -1,7 +1,19 @@ +
+ sync already runned + bad response +
+
diff --git a/server/handler/login.go b/server/handler/login.go index fdae48a5a..285702d09 100644 --- a/server/handler/login.go +++ b/server/handler/login.go @@ -4,12 +4,12 @@ import ( "encoding/json" "log" "net/http" - "time" "github.com/drone/drone/plugin/remote" "github.com/drone/drone/server/capability" "github.com/drone/drone/server/datastore" "github.com/drone/drone/server/session" + "github.com/drone/drone/server/sync" "github.com/drone/drone/shared/model" "github.com/goji/context" "github.com/zenazn/goji/web" @@ -83,7 +83,8 @@ func GetLogin(c web.C, w http.ResponseWriter, r *http.Request) { u.Secret = login.Secret u.Name = login.Name u.SetEmail(login.Email) - u.Syncing = true //u.IsStale() // todo (badrydzewski) should not always sync + u.Syncing = u.IsStale() + if err := datastore.PutUser(ctx, u); err != nil { log.Println(err) w.WriteHeader(http.StatusBadRequest) @@ -102,51 +103,8 @@ func GetLogin(c web.C, w http.ResponseWriter, r *http.Request) { redirect = "/sync" log.Println("sync user account.", u.Login) - // sync inside a goroutine. This should eventually be moved to - // its own package / sync utility. - go func() { - repos, err := remote.GetRepos(u) - if err != nil { - log.Println("Error syncing user account, listing repositories", u.Login, err) - return - } - - // insert all repositories - for _, repo := range repos { - var role = repo.Role - if err := datastore.PostRepo(ctx, repo); err != nil { - // typically we see a failure because the repository already exists - // in which case, we can retrieve the existing record to get the ID. - repo, err = datastore.GetRepoName(ctx, repo.Host, repo.Owner, repo.Name) - if err != nil { - log.Println("Error adding repo.", u.Login, repo.Name, err) - continue - } - } - - // add user permissions - perm := model.Perm{ - UserID: u.ID, - RepoID: repo.ID, - Read: role.Read, - Write: role.Write, - Admin: role.Admin, - } - if err := datastore.PostPerm(ctx, &perm); err != nil { - log.Println("Error adding permissions.", u.Login, repo.Name, err) - continue - } - - log.Println("Successfully syced repo.", u.Login+"/"+repo.Name) - } - - u.Synced = time.Now().UTC().Unix() - u.Syncing = false - if err := datastore.PutUser(ctx, u); err != nil { - log.Println("Error syncing user account, updating sync date", u.Login, err) - return - } - }() + // sync inside a goroutine + go sync.SyncUser(ctx, u, remote) } token, err := session.GenerateToken(ctx, r, u) diff --git a/server/handler/user.go b/server/handler/user.go index 3b0b64dae..e5e8a7348 100644 --- a/server/handler/user.go +++ b/server/handler/user.go @@ -4,7 +4,9 @@ import ( "encoding/json" "net/http" + "github.com/drone/drone/plugin/remote" "github.com/drone/drone/server/datastore" + "github.com/drone/drone/server/sync" "github.com/drone/drone/shared/model" "github.com/goji/context" "github.com/zenazn/goji/web" @@ -111,3 +113,37 @@ func GetUserFeed(c web.C, w http.ResponseWriter, r *http.Request) { } json.NewEncoder(w).Encode(&repos) } + +// PostUserSync accepts a request to post user sync +// +// POST /api/user/sync +// +func PostUserSync(c web.C, w http.ResponseWriter, r *http.Request) { + var ctx = context.FromC(c) + var user = ToUser(c) + if user == nil { + w.WriteHeader(http.StatusUnauthorized) + return + } + + var remote = remote.Lookup(user.Remote) + if remote == nil { + w.WriteHeader(http.StatusNotFound) + return + } + + if user.Syncing { + w.WriteHeader(http.StatusConflict) + return + } + + user.Syncing = true + if err := datastore.PutUser(ctx, user); err != nil { + w.WriteHeader(http.StatusNotFound) + return + } + + go sync.SyncUser(ctx, user, remote) + w.WriteHeader(http.StatusNoContent) + return +} diff --git a/server/router/router.go b/server/router/router.go index 418256d19..651a0146c 100644 --- a/server/router/router.go +++ b/server/router/router.go @@ -58,6 +58,7 @@ func New() *web.Mux { user.Use(middleware.RequireUser) user.Get("/api/user/feed", handler.GetUserFeed) user.Get("/api/user/repos", handler.GetUserRepos) + user.Post("/api/user/sync", handler.PostUserSync) user.Get("/api/user", handler.GetUserCurrent) user.Put("/api/user", handler.PutUser) mux.Handle("/api/user*", user) diff --git a/server/sync/sync.go b/server/sync/sync.go new file mode 100644 index 000000000..b926dc234 --- /dev/null +++ b/server/sync/sync.go @@ -0,0 +1,55 @@ +package sync + +import ( + "log" + "time" + + "code.google.com/p/go.net/context" + "github.com/drone/drone/plugin/remote" + "github.com/drone/drone/server/datastore" + "github.com/drone/drone/shared/model" +) + +func SyncUser(ctx context.Context, user *model.User, remote remote.Remote) { + repos, err := remote.GetRepos(user) + if err != nil { + log.Println("Error syncing user account, listing repositories", user.Login, err) + return + } + + // insert all repositories + for _, repo := range repos { + var role = repo.Role + if err := datastore.PostRepo(ctx, repo); err != nil { + // typically we see a failure because the repository already exists + // in which case, we can retrieve the existing record to get the ID. + repo, err = datastore.GetRepoName(ctx, repo.Host, repo.Owner, repo.Name) + if err != nil { + log.Println("Error adding repo.", user.Login, repo.Name, err) + continue + } + } + + // add user permissions + perm := model.Perm{ + UserID: user.ID, + RepoID: repo.ID, + Read: role.Read, + Write: role.Write, + Admin: role.Admin, + } + if err := datastore.PostPerm(ctx, &perm); err != nil { + log.Println("Error adding permissions.", user.Login, repo.Name, err) + continue + } + + log.Println("Successfully syced repo.", user.Login+"/"+repo.Name) + } + + user.Synced = time.Now().UTC().Unix() + user.Syncing = false + if err := datastore.PutUser(ctx, user); err != nil { + log.Println("Error syncing user account, updating sync date", user.Login, err) + return + } +}