From d3448badd6871a6026342db8bfcbd5f7330825b7 Mon Sep 17 00:00:00 2001 From: Matt Martz Date: Wed, 21 May 2014 11:53:51 -0500 Subject: [PATCH 1/8] Make target optional in the swift publish plugin This allows uploading a directory to the root of a container. If uploading a file and you do not specify a target, nothing will actually be uploaded. --- pkg/plugin/publish/swift.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pkg/plugin/publish/swift.go b/pkg/plugin/publish/swift.go index d2a881245..f5d76b881 100644 --- a/pkg/plugin/publish/swift.go +++ b/pkg/plugin/publish/swift.go @@ -2,6 +2,7 @@ package publish import ( "fmt" + "strings" "github.com/drone/drone/pkg/build/buildfile" ) @@ -37,12 +38,18 @@ type Swift struct { } func (s *Swift) Write(f *buildfile.Buildfile) { + var target string // All options are required, so ensure they are present - if len(s.Username) == 0 || len(s.Password) == 0 || len(s.AuthURL) == 0 || len(s.Region) == 0 || len(s.Source) == 0 || len(s.Container) == 0 || len(s.Target) == 0 { + if len(s.Username) == 0 || len(s.Password) == 0 || len(s.AuthURL) == 0 || len(s.Region) == 0 || len(s.Source) == 0 || len(s.Container) == 0 { f.WriteCmdSilent(`echo "Swift: Missing argument(s)"`) return } + // If a target was provided, prefix it with a / + if len(s.Target) > 0 { + target = fmt.Sprintf("/%s", strings.TrimPrefix(s.Target, "/")) + } + // debugging purposes so we can see if / where something is failing f.WriteCmdSilent(`echo "Swift: Publishing..."`) @@ -56,5 +63,5 @@ func (s *Swift) Write(f *buildfile.Buildfile) { f.WriteEnv("SWIFTLY_AUTH_KEY", s.Password) f.WriteEnv("SWIFTLY_REGION", s.Region) - f.WriteCmd(fmt.Sprintf(`swiftly put -i %s %s/%s`, s.Source, s.Container, s.Target)) + f.WriteCmd(fmt.Sprintf(`swiftly put -i %s %s%s`, s.Source, s.Container, target)) } From 913332d965de7f5bc64d9d15147c279ca10ac2d1 Mon Sep 17 00:00:00 2001 From: Michael Nutt Date: Thu, 22 May 2014 23:16:01 -0400 Subject: [PATCH 2/8] save drone.yml on the build --- pkg/database/builds.go | 6 ++-- .../migrate/20140522205400_save_drone_yml.go | 19 ++++++++++ pkg/database/migrate/all.go | 1 + pkg/handler/bitbucket.go | 14 ++------ pkg/handler/github.go | 35 ++++--------------- pkg/handler/gitlab.go | 31 ++++------------ pkg/handler/testing/handler_test.go | 4 +-- pkg/model/build.go | 21 +++++------ pkg/queue/queue.go | 5 --- pkg/queue/worker.go | 26 +++++++++----- 10 files changed, 67 insertions(+), 95 deletions(-) create mode 100644 pkg/database/migrate/20140522205400_save_drone_yml.go diff --git a/pkg/database/builds.go b/pkg/database/builds.go index fefe1e7bb..d3fc4f722 100644 --- a/pkg/database/builds.go +++ b/pkg/database/builds.go @@ -10,7 +10,7 @@ const buildTable = "builds" // SQL Queries to retrieve a list of all Commits belonging to a Repo. const buildStmt = ` -SELECT id, commit_id, slug, status, started, finished, duration, created, updated, stdout +SELECT id, commit_id, slug, status, started, finished, duration, created, updated, stdout, buildscript FROM builds WHERE commit_id = ? ORDER BY slug ASC @@ -18,7 +18,7 @@ ORDER BY slug ASC // SQL Queries to retrieve a Build by id. const buildFindStmt = ` -SELECT id, commit_id, slug, status, started, finished, duration, created, updated, stdout +SELECT id, commit_id, slug, status, started, finished, duration, created, updated, stdout, buildscript FROM builds WHERE id = ? LIMIT 1 @@ -26,7 +26,7 @@ LIMIT 1 // SQL Queries to retrieve a Commit by name and repo id. const buildFindSlugStmt = ` -SELECT id, commit_id, slug, status, started, finished, duration, created, updated, stdout +SELECT id, commit_id, slug, status, started, finished, duration, created, updated, stdout, buildscript FROM builds WHERE slug = ? AND commit_id = ? LIMIT 1 diff --git a/pkg/database/migrate/20140522205400_save_drone_yml.go b/pkg/database/migrate/20140522205400_save_drone_yml.go new file mode 100644 index 000000000..443dc7164 --- /dev/null +++ b/pkg/database/migrate/20140522205400_save_drone_yml.go @@ -0,0 +1,19 @@ +package migrate + +type rev20140522205400 struct{} + +var SaveDroneYml = &rev20140522205400{} + +func (r *rev20140522205400) Revision() int64 { + return 20140522205400 +} + +func (r *rev20140522205400) Up(mg *MigrationDriver) error { + _, err := mg.AddColumn("builds", "buildscript TEXT") + return err +} + +func (r *rev20140522205400) Down(mg *MigrationDriver) error { + _, err := mg.DropColumns("builds", "buildscript") + return err +} diff --git a/pkg/database/migrate/all.go b/pkg/database/migrate/all.go index c745a4edb..14c6a9a74 100644 --- a/pkg/database/migrate/all.go +++ b/pkg/database/migrate/all.go @@ -13,6 +13,7 @@ func (m *Migration) All() *Migration { m.Add(GitHubEnterpriseSupport) m.Add(AddOpenInvitationColumn) m.Add(AddGitlabColumns) + m.Add(SaveDroneYml) // m.Add(...) // ... diff --git a/pkg/handler/bitbucket.go b/pkg/handler/bitbucket.go index 48ab7f2cd..be88727bf 100644 --- a/pkg/handler/bitbucket.go +++ b/pkg/handler/bitbucket.go @@ -5,7 +5,6 @@ import ( "net/http" "time" - "github.com/drone/drone/pkg/build/script" "github.com/drone/drone/pkg/database" . "github.com/drone/drone/pkg/model" "github.com/drone/drone/pkg/queue" @@ -83,16 +82,6 @@ func (h *BitbucketHandler) Hook(w http.ResponseWriter, r *http.Request) error { return RenderText(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) } - // parse the build script - buildscript, err := script.ParseBuild([]byte(raw.Data), repo.Params) - if err != nil { - msg := "Could not parse your .drone.yml file. It needs to be a valid drone yaml file.\n\n" + err.Error() + "\n" - if err := saveFailedBuild(commit, msg); err != nil { - return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - } - return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) - } - // save the commit to the database if err := database.SaveCommit(commit); err != nil { return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -104,12 +93,13 @@ func (h *BitbucketHandler) Hook(w http.ResponseWriter, r *http.Request) error { build.CommitID = commit.ID build.Created = time.Now().UTC() build.Status = "Pending" + build.BuildScript = raw.Data if err := database.SaveBuild(build); err != nil { return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } // send the build to the queue - h.queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build, Script: buildscript}) + h.queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build}) // OK! return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK) diff --git a/pkg/handler/github.go b/pkg/handler/github.go index 14baf5c5b..0e0237ec1 100644 --- a/pkg/handler/github.go +++ b/pkg/handler/github.go @@ -6,7 +6,6 @@ import ( "strconv" "time" - "github.com/drone/drone/pkg/build/script" "github.com/drone/drone/pkg/database" . "github.com/drone/drone/pkg/model" "github.com/drone/drone/pkg/queue" @@ -131,22 +130,8 @@ func (h *GithubHandler) Hook(w http.ResponseWriter, r *http.Request) error { } // decode the content. Note: Not sure this will ever happen...it basically means a GitHub API issue - raw, err := content.DecodeContent() + buildscript, err := content.DecodeContent() if err != nil { - msg := "Could not decode the yaml from GitHub. Check that your .drone.yml is a valid yaml file.\n" - if err := saveFailedBuild(commit, msg); err != nil { - return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - } - return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) - } - - // parse the build script - buildscript, err := script.ParseBuild(raw, repo.Params) - if err != nil { - msg := "Could not parse your .drone.yml file. It needs to be a valid drone yaml file.\n\n" + err.Error() + "\n" - if err := saveFailedBuild(commit, msg); err != nil { - return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - } return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) } @@ -161,6 +146,7 @@ func (h *GithubHandler) Hook(w http.ResponseWriter, r *http.Request) error { build.CommitID = commit.ID build.Created = time.Now().UTC() build.Status = "Pending" + build.BuildScript = string(buildscript) if err := database.SaveBuild(build); err != nil { return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } @@ -169,7 +155,7 @@ func (h *GithubHandler) Hook(w http.ResponseWriter, r *http.Request) error { //realtime.CommitPending(repo.UserID, repo.TeamID, repo.ID, commit.ID, repo.Private) //realtime.BuildPending(repo.UserID, repo.TeamID, repo.ID, commit.ID, build.ID, repo.Private) - h.queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build, Script: buildscript}) //Push(repo, commit, build, buildscript) + h.queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build}) //Push(repo, commit, build) // OK! return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK) @@ -250,22 +236,12 @@ func (h *GithubHandler) PullRequestHook(w http.ResponseWriter, r *http.Request) } // decode the content - raw, err := content.DecodeContent() + buildscript, err := content.DecodeContent() if err != nil { RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } - // parse the build script - buildscript, err := script.ParseBuild(raw, repo.Params) - if err != nil { - // TODO if the YAML is invalid we should create a commit record - // with an ERROR status so that the user knows why a build wasn't - // triggered in the system - RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) - return - } - // save the commit to the database if err := database.SaveCommit(commit); err != nil { RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -278,6 +254,7 @@ func (h *GithubHandler) PullRequestHook(w http.ResponseWriter, r *http.Request) build.CommitID = commit.ID build.Created = time.Now().UTC() build.Status = "Pending" + build.BuildScript = string(buildscript) if err := database.SaveBuild(build); err != nil { RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return @@ -285,7 +262,7 @@ func (h *GithubHandler) PullRequestHook(w http.ResponseWriter, r *http.Request) // notify websocket that a new build is pending // TODO we should, for consistency, just put this inside Queue.Add() - h.queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build, Script: buildscript}) + h.queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build}) // OK! RenderText(w, http.StatusText(http.StatusOK), http.StatusOK) diff --git a/pkg/handler/gitlab.go b/pkg/handler/gitlab.go index f22c9d880..ae0e63102 100644 --- a/pkg/handler/gitlab.go +++ b/pkg/handler/gitlab.go @@ -9,7 +9,6 @@ import ( "strings" "time" - "github.com/drone/drone/pkg/build/script" "github.com/drone/drone/pkg/database" . "github.com/drone/drone/pkg/model" "github.com/drone/drone/pkg/queue" @@ -207,7 +206,7 @@ func (g *GitlabHandler) Hook(w http.ResponseWriter, r *http.Request) error { // get the drone.yml file from GitHub client := gogitlab.NewGitlab(settings.GitlabApiUrl, g.apiPath, user.GitlabToken) - content, err := client.RepoRawFile(ns(repo.Owner, repo.Name), commit.Hash, ".drone.yml") + buildscript, err := client.RepoRawFile(ns(repo.Owner, repo.Name), commit.Hash, ".drone.yml") if err != nil { msg := "No .drone.yml was found in this repository. You need to add one.\n" if err := saveFailedBuild(commit, msg); err != nil { @@ -216,16 +215,6 @@ func (g *GitlabHandler) Hook(w http.ResponseWriter, r *http.Request) error { return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) } - // parse the build script - buildscript, err := script.ParseBuild(content, repo.Params) - if err != nil { - msg := "Could not parse your .drone.yml file. It needs to be a valid drone yaml file.\n\n" + err.Error() + "\n" - if err := saveFailedBuild(commit, msg); err != nil { - return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - } - return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) - } - // save the commit to the database if err := database.SaveCommit(commit); err != nil { return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -237,11 +226,12 @@ func (g *GitlabHandler) Hook(w http.ResponseWriter, r *http.Request) error { build.CommitID = commit.ID build.Created = time.Now().UTC() build.Status = "Pending" + build.BuildScript = string(buildscript) if err := database.SaveBuild(build); err != nil { return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } - g.queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build, Script: buildscript}) + g.queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build}) // OK! return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK) @@ -287,7 +277,7 @@ func (g *GitlabHandler) PullRequestHook(p *gogitlab.HookPayload, repo *Repo, use commit.Timestamp = src.Commit.AuthoredDateRaw commit.SetAuthor(src.Commit.Author.Email) - content, err := client.RepoRawFile(strconv.Itoa(obj.SourceProjectId), commit.Hash, ".drone.yml") + buildscript, err := client.RepoRawFile(strconv.Itoa(obj.SourceProjectId), commit.Hash, ".drone.yml") if err != nil { msg := "No .drone.yml was found in this repository. You need to add one.\n" if err := saveFailedBuild(commit, msg); err != nil { @@ -296,16 +286,6 @@ func (g *GitlabHandler) PullRequestHook(p *gogitlab.HookPayload, repo *Repo, use return fmt.Errorf("Error to fetch build script: %q", err) } - // parse the build script - buildscript, err := script.ParseBuild(content, repo.Params) - if err != nil { - msg := "Could not parse your .drone.yml file. It needs to be a valid drone yaml file.\n\n" + err.Error() + "\n" - if err := saveFailedBuild(commit, msg); err != nil { - return fmt.Errorf("Failed to save build: %q", err) - } - return fmt.Errorf("Failed to parse build script: %q", err) - } - // save the commit to the database if err := database.SaveCommit(commit); err != nil { return fmt.Errorf("Failed to save commit: %q", err) @@ -317,11 +297,12 @@ func (g *GitlabHandler) PullRequestHook(p *gogitlab.HookPayload, repo *Repo, use build.CommitID = commit.ID build.Created = time.Now().UTC() build.Status = "Pending" + build.BuildScript = string(buildscript) if err := database.SaveBuild(build); err != nil { return fmt.Errorf("Failed to save build: %q", err) } - g.queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build, Script: buildscript}) + g.queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build}) return nil } diff --git a/pkg/handler/testing/handler_test.go b/pkg/handler/testing/handler_test.go index 9ea71824d..0616d9616 100644 --- a/pkg/handler/testing/handler_test.go +++ b/pkg/handler/testing/handler_test.go @@ -45,7 +45,7 @@ func TestRepoHandler(t *testing.T) { So(rec.Code, ShouldEqual, 303) }) Convey("Private repo can not be viewed by a non team member", func() { - req, err := http.NewRequest("GET", "/github.com/drone/drone", nil) + req, err := http.NewRequest("GET", "/github.com/drone/drone", nil) So(err, ShouldBeNil) rec := httptest.NewRecorder() setUserSession(rec, req, "rick@el.to.ro") @@ -61,7 +61,7 @@ func dummyUserRepo(w http.ResponseWriter, r *http.Request, u *User, repo *Repo) func setUserSession(w http.ResponseWriter, r *http.Request, username string) { handler.SetCookie(w, r, "_sess", username) - resp := http.Response{Header: w.Header()} + resp := http.Response{Header: w.Header()} for _, v := range resp.Cookies() { r.AddCookie(v) } diff --git a/pkg/model/build.go b/pkg/model/build.go index 44864156e..6dd0e2b6f 100644 --- a/pkg/model/build.go +++ b/pkg/model/build.go @@ -15,16 +15,17 @@ const ( ) type Build struct { - ID int64 `meddler:"id,pk" json:"id"` - CommitID int64 `meddler:"commit_id" json:"-"` - Slug string `meddler:"slug" json:"slug"` - Status string `meddler:"status" json:"status"` - Started time.Time `meddler:"started,utctime" json:"started"` - Finished time.Time `meddler:"finished,utctime" json:"finished"` - Duration int64 `meddler:"duration" json:"duration"` - Created time.Time `meddler:"created,utctime" json:"created"` - Updated time.Time `meddler:"updated,utctime" json:"updated"` - Stdout string `meddler:"stdout" json:"-"` + ID int64 `meddler:"id,pk" json:"id"` + CommitID int64 `meddler:"commit_id" json:"-"` + Slug string `meddler:"slug" json:"slug"` + Status string `meddler:"status" json:"status"` + Started time.Time `meddler:"started,utctime" json:"started"` + Finished time.Time `meddler:"finished,utctime" json:"finished"` + Duration int64 `meddler:"duration" json:"duration"` + Created time.Time `meddler:"created,utctime" json:"created"` + Updated time.Time `meddler:"updated,utctime" json:"updated"` + Stdout string `meddler:"stdout" json:"-"` + BuildScript string `meddler:"buildscript" json:"-"` } // HumanDuration returns a human-readable approximation of a duration diff --git a/pkg/queue/queue.go b/pkg/queue/queue.go index cb7cfba96..587434634 100644 --- a/pkg/queue/queue.go +++ b/pkg/queue/queue.go @@ -1,7 +1,6 @@ package queue import ( - "github.com/drone/drone/pkg/build/script" . "github.com/drone/drone/pkg/model" ) @@ -16,10 +15,6 @@ type BuildTask struct { Repo *Repo Commit *Commit Build *Build - - // Build instructions from the .drone.yml - // file, unmarshalled. - Script *script.Build } // Start N workers with the given build runner. diff --git a/pkg/queue/worker.go b/pkg/queue/worker.go index bf9aeab38..71adb15bd 100644 --- a/pkg/queue/worker.go +++ b/pkg/queue/worker.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/drone/drone/pkg/build/git" r "github.com/drone/drone/pkg/build/repo" + "github.com/drone/drone/pkg/build/script" "github.com/drone/drone/pkg/channel" "github.com/drone/drone/pkg/database" . "github.com/drone/drone/pkg/model" @@ -83,9 +84,16 @@ func (w *worker) execute(task *BuildTask) error { Host: settings.URL().String(), } + // parse the build script + buildscript, err := script.ParseBuild([]byte(task.Build.BuildScript), task.Repo.Params) + if err != nil { + log.Printf("Could not parse your .drone.yml file. It needs to be a valid drone yaml file.\n\n" + err.Error() + "\n") + return err + } + // send all "started" notifications - if task.Script.Notifications != nil { - task.Script.Notifications.Send(context) + if buildscript.Notifications != nil { + buildscript.Notifications.Send(context) } // Send "started" notification to Github @@ -113,7 +121,7 @@ func (w *worker) execute(task *BuildTask) error { // this is not a pull request (for security purposes) if task.Repo.Params != nil && len(task.Commit.PullRequest) == 0 { for k, v := range task.Repo.Params { - task.Script.Env = append(task.Script.Env, k+"="+v) + buildscript.Env = append(buildscript.Env, k+"="+v) } } @@ -126,7 +134,7 @@ func (w *worker) execute(task *BuildTask) error { }() // execute the build - passed, buildErr := w.runBuild(task, buf) + passed, buildErr := w.runBuild(task, buildscript, buf) task.Build.Finished = time.Now().UTC() task.Commit.Finished = time.Now().UTC() @@ -162,14 +170,14 @@ func (w *worker) execute(task *BuildTask) error { channel.Close(consoleslug) // send all "finished" notifications - if task.Script.Notifications != nil { - task.Script.Notifications.Send(context) + if buildscript.Notifications != nil { + buildscript.Notifications.Send(context) } return nil } -func (w *worker) runBuild(task *BuildTask, buf io.Writer) (bool, error) { +func (w *worker) runBuild(task *BuildTask, buildscript *script.Build, buf io.Writer) (bool, error) { repo := &r.Repo{ Name: task.Repo.Slug, Path: task.Repo.URL, @@ -177,11 +185,11 @@ func (w *worker) runBuild(task *BuildTask, buf io.Writer) (bool, error) { Commit: task.Commit.Hash, PR: task.Commit.PullRequest, Dir: filepath.Join("/var/cache/drone/src", task.Repo.Slug), - Depth: git.GitDepth(task.Script.Git), + Depth: git.GitDepth(buildscript.Git), } return w.runner.Run( - task.Script, + buildscript, repo, []byte(task.Repo.PrivateKey), task.Repo.Privileged, From fbdb330d5ce68d8dfec188adab065c7eb1ca68c3 Mon Sep 17 00:00:00 2001 From: Michael Nutt Date: Thu, 22 May 2014 23:52:03 -0400 Subject: [PATCH 3/8] add rebuild button for users who have admin access to the repo --- cmd/droned/assets/css/drone.css | 8 +++ cmd/droned/assets/css/drone.less | 15 +++--- cmd/droned/drone.go | 3 ++ pkg/database/repos.go | 15 ++++++ pkg/handler/commits.go | 84 ++++++++++++++++++++++++++--- pkg/handler/handler.go | 8 ++- pkg/template/pages/repo_commit.html | 12 +++++ 7 files changed, 127 insertions(+), 18 deletions(-) diff --git a/cmd/droned/assets/css/drone.css b/cmd/droned/assets/css/drone.css index 71fcae13b..7dfec911c 100644 --- a/cmd/droned/assets/css/drone.css +++ b/cmd/droned/assets/css/drone.css @@ -870,6 +870,14 @@ pre { font-size: 22px !IMPORTANT; line-height: 32px !IMPORTANT; } +.alert.alert-build-Success .actions, +.alert.alert-build-Error .actions, +.alert.alert-build-Failure .actions, +.alert.alert-build-Pending .actions, +.alert.alert-build-Started .actions { + float: right; + margin-top: -2px; +} .build-details { background: #FFF; margin-bottom: 40px; diff --git a/cmd/droned/assets/css/drone.less b/cmd/droned/assets/css/drone.less index 7d3fbe684..61f5484e9 100644 --- a/cmd/droned/assets/css/drone.less +++ b/cmd/droned/assets/css/drone.less @@ -184,7 +184,7 @@ body { border:0px; } -// +// // nav-repos // -------------------------------------------- @@ -364,7 +364,7 @@ body { margin-bottom: 10px; } -// +// // build list // -------------------------------------------- @@ -493,7 +493,7 @@ body { .btn.btn-Success { background:rgba(81, 163, 81, 0.75); } -.btn.btn-failure, +.btn.btn-failure, .btn.btn-Failure, .btn.btn-Error { background:rgba(189, 54, 47, 0.8); @@ -911,7 +911,7 @@ textarea { top: 6px; transition: all .15s ease; width: 35px; - z-index: 3; + z-index: 3; border-radius:5px; } @@ -1021,6 +1021,11 @@ pre { line-height: 32px !IMPORTANT; } } + + .actions { + float: right; + margin-top: -2px; + } } .build-details { @@ -1285,5 +1290,3 @@ pre { background: #999; cursor: pointer; } - - diff --git a/cmd/droned/drone.go b/cmd/droned/drone.go index b5641af8c..cea951b6a 100644 --- a/cmd/droned/drone.go +++ b/cmd/droned/drone.go @@ -136,6 +136,7 @@ func setupHandlers() { github = handler.NewGithubHandler(queue) gitlab = handler.NewGitlabHandler(queue) bitbucket = handler.NewBitbucketHandler(queue) + rebuild = handler.NewCommitRebuildHandler(queue) ) m := pat.New() @@ -226,7 +227,9 @@ func setupHandlers() { // handlers for repository, commits and build details m.Get("/:host/:owner/:name/commit/:commit/build/:label/out.txt", handler.RepoHandler(handler.BuildOut)) + m.Post("/:host/:owner/:name/commit/:commit/build/:label/rebuild", handler.RepoAdminHandler(rebuild.CommitRebuild)) m.Get("/:host/:owner/:name/commit/:commit/build/:label", handler.RepoHandler(handler.CommitShow)) + m.Post("/:host/:owner/:name/commit/:commit/rebuild", handler.RepoAdminHandler(rebuild.CommitRebuild)) m.Get("/:host/:owner/:name/commit/:commit", handler.RepoHandler(handler.CommitShow)) m.Get("/:host/:owner/:name/tree", handler.RepoHandler(handler.RepoDashboard)) m.Get("/:host/:owner/:name/status.svg", handler.ErrorHandler(handler.Badge)) diff --git a/pkg/database/repos.go b/pkg/database/repos.go index 0d9e2f15e..a344a3d67 100644 --- a/pkg/database/repos.go +++ b/pkg/database/repos.go @@ -90,3 +90,18 @@ func ListReposTeam(id int64) ([]*Repo, error) { err := meddler.QueryAll(db, &repos, repoTeamStmt, id) return repos, err } + +// Checks whether a user is admin of a repo +// Returns true if user owns repo or is on team that owns repo +// Returns true if the user is an admin member of the team. +func IsRepoAdmin(user *User, repo *Repo) (bool, error) { + if user == nil { + return false, nil + } + + if user.ID == repo.UserID { + return true, nil + } + + return IsMemberAdmin(user.ID, repo.TeamID) +} diff --git a/pkg/handler/commits.go b/pkg/handler/commits.go index c41620038..aad1120b8 100644 --- a/pkg/handler/commits.go +++ b/pkg/handler/commits.go @@ -8,6 +8,7 @@ import ( "github.com/drone/drone/pkg/channel" "github.com/drone/drone/pkg/database" . "github.com/drone/drone/pkg/model" + "github.com/drone/drone/pkg/queue" ) // Display a specific Commit. @@ -33,14 +34,20 @@ func CommitShow(w http.ResponseWriter, r *http.Request, u *User, repo *Repo) err return err } + admin, err := database.IsRepoAdmin(u, repo) + if err != nil { + return err + } + data := struct { - User *User - Repo *Repo - Commit *Commit - Build *Build - Builds []*Build - Token string - }{u, repo, commit, builds[0], builds, ""} + User *User + Repo *Repo + Commit *Commit + Build *Build + Builds []*Build + Token string + IsAdmin bool + }{u, repo, commit, builds[0], builds, "", admin} // get the specific build requested by the user. instead // of a database round trip, we can just loop through the @@ -94,3 +101,66 @@ func saveFailedBuild(commit *Commit, msg string) error { return nil } + +type CommitRebuildHandler struct { + queue *queue.Queue +} + +func NewCommitRebuildHandler(queue *queue.Queue) *CommitRebuildHandler { + return &CommitRebuildHandler{ + queue: queue, + } +} + +// CommitRebuild re-queues a previously built commit. It finds the existing +// commit and build and injects them back into the queue. If the commit +// doesn't exist or has no builds, or if a build label has been passed but +// can't be located, it prints an error. Otherwise, it adds the build/commit +// to the queue and redirects back to the commit page. +func (h *CommitRebuildHandler) CommitRebuild(w http.ResponseWriter, r *http.Request, u *User, repo *Repo) error { + hash := r.FormValue(":commit") + labl := r.FormValue(":label") + host := r.FormValue(":host") + + // get the commit from the database + commit, err := database.GetCommitHash(hash, repo.ID) + if err != nil { + return err + } + + // get the builds from the database. a commit can have + // multiple sub-builds (or matrix builds) + builds, err := database.ListBuilds(commit.ID) + if err != nil { + return err + } + + build := builds[0] + + if labl != "" { + // get the specific build requested by the user. instead + // of a database round trip, we can just loop through the + // list and extract the requested build. + build = nil + for _, b := range builds { + if b.Slug == labl { + build = b + break + } + } + } + + if build == nil { + return fmt.Errorf("Could not find build: %s", labl) + } + + h.queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build}) + + if labl != "" { + http.Redirect(w, r, fmt.Sprintf("/%s/%s/%s/commit/%s/build/%s", host, repo.Owner, repo.Name, hash, labl), http.StatusSeeOther) + } else { + http.Redirect(w, r, fmt.Sprintf("/%s/%s/%s/commit/%s", host, repo.Owner, repo.Name, hash), http.StatusSeeOther) + } + + return nil +} diff --git a/pkg/handler/handler.go b/pkg/handler/handler.go index 2af9150c9..40de6fd3c 100644 --- a/pkg/handler/handler.go +++ b/pkg/handler/handler.go @@ -142,11 +142,9 @@ func (h RepoAdminHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // The User must own the repository OR be a member // of the Team that owns the repository. - if user.ID != repo.UserID { - if admin, _ := database.IsMemberAdmin(user.ID, repo.TeamID); admin == false { - RenderNotFound(w) - return - } + if admin, _ := database.IsRepoAdmin(user, repo); admin == false { + RenderNotFound(w) + return } if err = h(w, r, user, repo); err != nil { diff --git a/pkg/template/pages/repo_commit.html b/pkg/template/pages/repo_commit.html index 2dfd38b34..8a743f6f2 100644 --- a/pkg/template/pages/repo_commit.html +++ b/pkg/template/pages/repo_commit.html @@ -24,6 +24,18 @@ {{ else }} commit {{ .Commit.HashShort }} to {{.Commit.Branch}} branch {{ end }} + +
+ {{ if .IsAdmin }} + {{ if not .Build.IsRunning }} +
+ +
+ {{ end }} + {{ end }} +
+
From ff98d747312e8a9107941bd140f31456d492b070 Mon Sep 17 00:00:00 2001 From: Michael Nutt Date: Fri, 23 May 2014 13:23:43 -0400 Subject: [PATCH 4/8] always specify a branch when rebuilding --- pkg/handler/commits.go | 10 +++++++--- pkg/template/pages/repo_commit.html | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/pkg/handler/commits.go b/pkg/handler/commits.go index aad1120b8..442b1f948 100644 --- a/pkg/handler/commits.go +++ b/pkg/handler/commits.go @@ -121,9 +121,13 @@ func (h *CommitRebuildHandler) CommitRebuild(w http.ResponseWriter, r *http.Requ hash := r.FormValue(":commit") labl := r.FormValue(":label") host := r.FormValue(":host") + branch := r.FormValue("branch") + if branch == "" { + branch = "master" + } // get the commit from the database - commit, err := database.GetCommitHash(hash, repo.ID) + commit, err := database.GetCommitBranchHash(branch, hash, repo.ID) if err != nil { return err } @@ -157,9 +161,9 @@ func (h *CommitRebuildHandler) CommitRebuild(w http.ResponseWriter, r *http.Requ h.queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build}) if labl != "" { - http.Redirect(w, r, fmt.Sprintf("/%s/%s/%s/commit/%s/build/%s", host, repo.Owner, repo.Name, hash, labl), http.StatusSeeOther) + http.Redirect(w, r, fmt.Sprintf("/%s/%s/%s/commit/%s/build/%s?branch=%s", host, repo.Owner, repo.Name, hash, labl, branch), http.StatusSeeOther) } else { - http.Redirect(w, r, fmt.Sprintf("/%s/%s/%s/commit/%s", host, repo.Owner, repo.Name, hash), http.StatusSeeOther) + http.Redirect(w, r, fmt.Sprintf("/%s/%s/%s/commit/%s?branch=%s", host, repo.Owner, repo.Name, hash, branch), http.StatusSeeOther) } return nil diff --git a/pkg/template/pages/repo_commit.html b/pkg/template/pages/repo_commit.html index 8a743f6f2..dfc41ca6d 100644 --- a/pkg/template/pages/repo_commit.html +++ b/pkg/template/pages/repo_commit.html @@ -28,7 +28,7 @@
{{ if .IsAdmin }} {{ if not .Build.IsRunning }} -
From 632b61adbffc472e3f0742ccc98aaed2a9577437 Mon Sep 17 00:00:00 2001 From: kibao Date: Thu, 29 May 2014 18:47:21 +0200 Subject: [PATCH 5/8] Update buildscript for old builds --- pkg/database/migrate/20140522205400_save_drone_yml.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/database/migrate/20140522205400_save_drone_yml.go b/pkg/database/migrate/20140522205400_save_drone_yml.go index 443dc7164..55a517a15 100644 --- a/pkg/database/migrate/20140522205400_save_drone_yml.go +++ b/pkg/database/migrate/20140522205400_save_drone_yml.go @@ -10,6 +10,7 @@ func (r *rev20140522205400) Revision() int64 { func (r *rev20140522205400) Up(mg *MigrationDriver) error { _, err := mg.AddColumn("builds", "buildscript TEXT") + _, err = mg.Tx.Exec("UPDATE builds SET buildscript = '' WHERE buildscript IS NULL") return err } From 21c95070684a5567e577d49b37d27b28f6108533 Mon Sep 17 00:00:00 2001 From: Martin Charles Date: Wed, 28 May 2014 17:50:19 -0400 Subject: [PATCH 6/8] Refactored Less StyleSheets * started using less's shortucts * removed vendor prefixes fron less file; use autoprefixer instead * fixed spacing * renamed all 0 values to 0 * converted all comments to silent comments --- AUTHORS | 2 + cmd/droned/assets/css/drone.css | 200 ++-- cmd/droned/assets/css/drone.less | 1819 +++++++++++++++--------------- 3 files changed, 969 insertions(+), 1052 deletions(-) diff --git a/AUTHORS b/AUTHORS index 9c01fe947..e16a25991 100644 --- a/AUTHORS +++ b/AUTHORS @@ -3,4 +3,6 @@ Artur Rodrigues Brad Rydzewski +Martin Charles Thomas Burke + diff --git a/cmd/droned/assets/css/drone.css b/cmd/droned/assets/css/drone.css index 7dfec911c..a47972298 100644 --- a/cmd/droned/assets/css/drone.css +++ b/cmd/droned/assets/css/drone.css @@ -1,14 +1,14 @@ -body { +body { background: #FFF; } .container { max-width: none !important; width: 940px; - padding: 0px; + padding: 0; } .row { - margin: 0px; - padding: 0px; + margin: 0; + padding: 0; } .col-xs-1, .col-xs-2, @@ -19,7 +19,7 @@ .col-xs-7, .col-xs-8, .col-xs-9 { - padding: 0px; + padding: 0; } .row > .col-xs-2:last-child, .row > .col-xs-3:last-child, @@ -36,7 +36,7 @@ margin-bottom: 0px; } .navbar-inverse .container { - padding-right: 0px; + padding-right: 0; } .navbar-fixed-top, .navbar-fixed-bottom { @@ -53,7 +53,7 @@ } .navbar .nav { float: right; - margin-right: 0px; + margin-right: 0; } .navbar .nav > li > a { padding: 18px 20px; @@ -63,10 +63,9 @@ margin: 0px; margin-top: 11px; border-radius: 4px; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; background: #363839; border: none; + -webkit-box-shadow: none; box-shadow: none; text-shadow: none; color: #DDD; @@ -82,7 +81,6 @@ border: none; position: relative; -webkit-box-shadow: none; - -moz-box-shadow: none; box-shadow: none; } .subhead h1 { @@ -110,8 +108,6 @@ width: 42px; height: 42px; border-radius: 50%; - -webkit-border-radius: 50%; - -moz-border-radius: 50%; } .subhead h1.user small { color: #777; @@ -119,14 +115,14 @@ } .subhead .container { position: relative; - padding: 0px; + padding: 0; } .subhead .nav-tabs { position: absolute; - right: 0px; - bottom: 0px; - margin: 0px; - padding: 0px; + right: 0; + bottom: 0; + margin: 0; + padding: 0; border-bottom: none; } .subhead .nav-tabs > li { @@ -136,11 +132,11 @@ color: #999; padding: 10px 20px; line-height: 20px; - border: 0px; + border: 0; } .subhead .nav-tabs > li.active > a { color: #777; - border: 0px; + border: 0; } .nav-repos, .nav-branches { @@ -154,11 +150,9 @@ padding: 5px 15px; padding: 10px 15px; border: none; - margin: 0px !IMPORTANT; + margin: 0 !IMPORTANT; font-size: 15px; - border-radius: 0px; - -moz-border-radius: 0px; - -webkit-border-radius: 0px; + border-radius: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; @@ -200,7 +194,7 @@ .nav-repos li a, .nav-branches li a { padding: 12px 15px; - margin-bottom: 0px; + margin-bottom: 0; color: #777; } .nav-pills.nav-repos > li > a, @@ -233,12 +227,12 @@ margin-top: -10px; } .alert .thumbnails { - margin: 0px; - padding: 0px; + margin: 0; + padding: 0; } .alert .thumbnails li { - margin: 0px; - padding: 0px; + margin: 0; + padding: 0; margin-left: 20px; margin-top: 20px; } @@ -261,9 +255,9 @@ font-weight: normal; } .alert.alert-feed .thumbnails > li { - margin: 0px; + margin: 0; margin-left: 5px; - padding: 0px; + padding: 0; display: inline-block; } .alert.alert-feed .thumbnails > li a { @@ -272,8 +266,6 @@ } .alert.alert-feed .thumbnails > li a img { border-radius: 50%; - -moz-border-radius: 50%; - -webkit-border-radius: 50%; width: 32px; height: 32px; margin-top: 10px; @@ -296,11 +288,11 @@ } .commit-list { list-style: none; - margin: 0px; - padding-left: 0px; + margin: 0; + padding-left: 0; } .commit-list > li { - margin: 0px; + margin: 0; position: relative; padding: 25px 20px; padding-left: 25px; @@ -310,8 +302,6 @@ position: absolute; right: 20px; top: 20px; - -webkit-border-radius: 50%; - -moz-border-radius: 50%; border-radius: 50%; width: 48px; height: 48px; @@ -359,8 +349,8 @@ .commit-list > li > h3 p { font-size: 14px; color: #999; - margin: 0px; - padding: 0px; + margin: 0; + padding: 0; padding-top: 2px; display: block; overflow: hidden; @@ -378,6 +368,7 @@ background: #BBB; color: #FFF; text-shadow: none; + -webkit-box-shadow: none; box-shadow: none; padding: 10px 15px; min-width: 75px; @@ -395,8 +386,6 @@ min-width: 48px; max-width: 48px; border-radius: 50%; - -webkit-border-radius: 50%; - -moz-border-radius: 50%; } .btn.btn-success, .btn.btn-Success { @@ -436,8 +425,6 @@ background: #f7f7f7; width: 75px; border-radius: 0px; - -webkit-border-radius: 0px; - -moz-border-radius: 0px; text-align: center; font-size: 22px; text-decoration: none; @@ -460,9 +447,6 @@ color: #FFF; display: inline-block; -webkit-animation: spin 1.5s infinite linear; - -moz-animation: spin 1.5s infinite linear; - -ms-animation: spin 1.5s infinite linear; - -o-animation: spin 1.5s infinite linear; animation: spin 1.5s infinite linear; } .btn.btn-mini { @@ -482,31 +466,6 @@ line-height: 24px !IMPORTANT; font-size: 14px !IMPORTANT; } -@-webkit-keyframes spin { - to { - -webkit-transform: rotate(360deg); - } -} -@-moz-keyframes spin { - to { - -moz-transform: rotate(360deg); - } -} -@-ms-keyframes spin { - to { - -ms-transform: rotate(360deg); - } -} -@-o-keyframes spin { - to { - -o-transform: rotate(360deg); - } -} -@keyframes spin { - to { - transform: rotate(360deg); - } -} .btn.btn-None { background: rgba(81, 163, 81, 0.75); border-bottom: 2px solid #51a351; @@ -539,6 +498,18 @@ background: rgba(189, 54, 47, 0.9); border-bottom: 2px solid #bd362f; } +@-webkit-keyframes spin { + to { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} +@keyframes spin { + to { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} .nav-pills > li a { padding: 7px 15px; color: #777777; @@ -546,14 +517,10 @@ .nav-pills > li > a, .nav-pills > li > a:hover, .nav-pills > li > a:focus { - border-radius: 0px; - -webkit-border-radius: 0px; - -moz-border-radius: 0px; + border-radius: 0; } .nav-pills > .active { - border-radius: 0px; - -webkit-border-radius: 0px; - -moz-border-radius: 0px; + border-radius: 0; } .nav-pills > li.active > a, .nav-pills > li.active > a:hover, @@ -561,10 +528,8 @@ .nav-pills > .active > a, .nav-pills > .active > a:hover, .nav-pills > .active > a:focus { - border-radius: 0px; - -webkit-border-radius: 0px; - -moz-border-radius: 0px; - margin-top: 0px; + border-radius: 0; + margin-top: 0; background-color: #f4f4f4; color: #777777; } @@ -578,12 +543,12 @@ form label { display: block; } form label:first-child { - margin-top: 0px; + margin-top: 0; } form .form-actions { background: transparent; padding: 30px 0px; - margin: 0px; + margin: 0; border: none; } form .form-actions .btn { @@ -601,8 +566,6 @@ select.form-control { font-size: 14px; line-height: 20px; color: #555555; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; border-radius: 4px; vertical-align: middle; width: 270px; @@ -624,7 +587,6 @@ span.form-control { background-color: #fcfcfc; border-color: #cccccc; -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); - -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); cursor: not-allowed; } @@ -666,22 +628,21 @@ textarea { font-size: 16px; } .row.gravatar-list { - margin-left: 0px; + margin-left: 0; } .row.gravatar-list .col-xs-3 { width: 23.404255319148934%; - margin: 0px; - padding: 0px; + margin: 0; + padding: 0; margin-right: 2.127659574468085%; } .row.gravatar-list .col-xs-3:nth-child(4) { - margin-right: 0px; + margin-right: 0; } .row.gravatar-list .col-xs-3 a.thumbnail, .row.gravatar-list .col-xs-3 a.thumbnail:hover { - border-radius: 0px; - -webkit-border-radius: 0px; - -moz-border-radius: 0px; + border-radius: 0; + -webkit-box-shadow: none; box-shadow: none; border: none; text-decoration: none; @@ -721,7 +682,11 @@ textarea { .row.gravatar-list .col-xs-3 a.thumbnail:hover.team-add .caption > h3 { color: #999; } -/* Checkbox hack */ +.switch { + height: 40px; + position: relative; + width: 100px; +} .switch input, .switch a { cursor: pointer; @@ -731,25 +696,18 @@ textarea { width: 100%; z-index: 100; } -/* Container */ -.switch { - height: 40px; - position: relative; - width: 100px; -} -/* Background unchecked */ .switch label { background: rgba(189, 54, 47, 0.8); border-radius: 2px; display: block; height: 100%; position: relative; + -webkit-transition: all .15s ease; transition: all .15s ease; width: 100%; border-radius: 5px; color: #FFF; } -/* Slider unchecked */ .switch label div { background: #fff; border-radius: 2px; @@ -758,12 +716,12 @@ textarea { left: 6px; position: absolute; top: 6px; + -webkit-transition: all .15s ease; transition: all .15s ease; width: 35px; z-index: 3; border-radius: 5px; } -/* Vertical lines on slider */ .switch label div:before { bottom: 0; content: ''; @@ -774,10 +732,10 @@ textarea { position: absolute; right: 0; top: 0; + -webkit-transition: all .15s ease; transition: all .15s ease; width: 3px; } -/* Icon styles */ .switch .fontawesome-ok { font-size: 18px; left: 15px; @@ -802,7 +760,6 @@ textarea { content: "\f00d"; opacity: 0.8; } -/* Checked States */ .switch input:checked ~ label { background: rgba(81, 163, 81, 0.75); } @@ -832,8 +789,6 @@ pre { font-size: 15px; padding: 15px 20px; border-radius: 5px; - -webkit-border-radius: 5px; - -moz-border-radius: 5px; } .alert.alert-build-Success span, .alert.alert-build-Error span, @@ -902,8 +857,6 @@ pre { } .build-details img { float: left; - -webkit-border-radius: 50%; - -moz-border-radius: 50%; border-radius: 50%; margin-right: 30px; width: 58px; @@ -944,7 +897,7 @@ pre { margin-bottom: 30px; } .form-repo .field-group label { - margin-top: 0px; + margin-top: 0; } .form-repo .field-separator { display: inline-block; @@ -952,11 +905,11 @@ pre { } .form-repo ul { padding-top: 15px; - margin-bottom: 0px; - padding-bottom: 0px; + margin-bottom: 0; + padding-bottom: 0; list-style: none; - padding-left: 0px; - margin-left: 0px; + padding-left: 0; + margin-left: 0; } .form-repo ul li { padding-bottom: 10px; @@ -965,18 +918,16 @@ pre { width: 32px; height: 32px; border-radius: 50%; - -webkit-border-radius: 50%; - -moz-border-radius: 50%; margin-left: 10px; margin-right: 10px; } ul.account-radio-group { padding-top: 15px; - margin-bottom: 0px; - padding-bottom: 0px; + margin-bottom: 0; + padding-bottom: 0; list-style: none; - padding-left: 0px; - margin-left: 0px; + padding-left: 0; + margin-left: 0; } ul.account-radio-group li { padding-bottom: 10px; @@ -985,8 +936,6 @@ ul.account-radio-group li img { width: 32px; height: 32px; border-radius: 50%; - -webkit-border-radius: 50%; - -moz-border-radius: 50%; margin-left: 10px; margin-right: 10px; } @@ -999,8 +948,6 @@ ul.account-radio-group li img { margin-top: 40px; padding: 0px 0px 20px 0px; border-radius: 5px; - -webkit-border-radius: 5px; - -moz-border-radius: 5px; } .form-centered h1 { background-color: #262829; @@ -1011,7 +958,7 @@ ul.account-radio-group li img { font-weight: normal; font-size: 18px; padding: 25px 10px; - margin-top: 0px; + margin-top: 0; margin-bottom: 30px; } .form-centered input[type="submit"] { @@ -1024,8 +971,6 @@ ul.account-radio-group li img { width: 280px; background: #AAA; border-radius: 5px; - -webkit-border-radius: 5px; - -moz-border-radius: 5px; } .form-centered input[type="text"], .form-centered input[type="email"], @@ -1052,6 +997,7 @@ ul.account-radio-group li img { .form-centered input[type="text"][disabled], .form-centered input[type="email"][disabled], .form-centered input[type="password"][disabled] { + -webkit-box-shadow: none; box-shadow: none; background: #f7f7f7; color: #999; diff --git a/cmd/droned/assets/css/drone.less b/cmd/droned/assets/css/drone.less index 61f5484e9..104b614df 100644 --- a/cmd/droned/assets/css/drone.less +++ b/cmd/droned/assets/css/drone.less @@ -9,17 +9,17 @@ body { // here we need to reset the container so that we are // displaying the page in non-responsive mode. .container { - max-width: none !important; - width: 940px; - padding:0px; + max-width: none !important; + width: 940px; + padding:0; } // as part of displaying the page in non-responsive // mode we need to remove some weird negative margins // that exist on the rows, and columns. .row { - margin:0px; - padding:0px; + margin:0; + padding:0; } .col-xs-1, @@ -31,250 +31,252 @@ body { .col-xs-7, .col-xs-8, .col-xs-9 { - padding:0px; + padding:0; } - -.row > .col-xs-2:last-child, -.row > .col-xs-3:last-child, -.row > .col-xs-4:last-child, -.row > .col-xs-5:last-child, -.row > .col-xs-6:last-child, -.row > .col-xs-7:last-child, -.row > .col-xs-8:last-child, -.row > .col-xs-9:last-child, { - padding-left:20px; +.col-xs-2, +.col-xs-3, +.col-xs-4, +.col-xs-5, +.col-xs-6, +.col-xs-7, +.col-xs-8, +.col-xs-9 { + .row > &:last-child { + padding-left:20px; + } } //--------------------------------------------------------------------- // Navigation Bar -.navbar-inverse { - background: #262829; - margin-bottom: 0px; - .container { - padding-right:0px; - } -} +.navbar { + &-inverse { + background: #262829; + margin-bottom: 0px; -.navbar-fixed-top, .navbar-fixed-bottom { - position:relative; -} + .container { + padding-right:0; + } + } -.navbar-inverse .navbar-brand { - padding: 21px 20px 0px 0px; - font-weight: normal; - text-shadow: none; - font-family: 'Orbitron'; - font-size: 28px; - color: #DDDDDD; - margin-left: 0px !IMPORTANT; -} + &-fixed-top, &-fixed-bottom { + position:relative; + } -.navbar .nav { - float:right; - margin-right:0px; -} + &-inverse &-brand { + padding: 21px 20px 0px 0px; + font-weight: normal; + text-shadow: none; + font-family: 'Orbitron'; + font-size: 28px; + color: #DDDDDD; + margin-left: 0px !IMPORTANT; + } -.navbar .nav > li > a { - padding: 18px 20px; -} + .nav { + float:right; + margin-right:0; -.navbar .nav > li > a.btn { - padding: 7px 20px; - margin: 0px; - margin-top:11px; - // TODO this should use a custom LESS function - border-radius:4px; - -webkit-border-radius:4px; - -moz-border-radius:4px; - background:#363839; - border: none; - box-shadow: none; - text-shadow: none; - color: #DDD; - margin-left: 15px; -} + > li > a { + padding: 18px 20px; -.navbar .nav > li > a.btn.btn-search, -.navbar .nav > li > a.btn.btn-config { - padding: 7px 15px; + &.btn { + padding: 7px 20px; + margin: 0px; + margin-top:11px; + border-radius:4px; + background:#363839; + border: none; + box-shadow: none; + text-shadow: none; + color: #DDD; + margin-left: 15px; + + &.btn-search, &.btn-config { + padding: 7px 15px; + } + } + } + } } //--------------------------------------------------------------------- // Subheader on (pretty much) every page .subhead { - margin-bottom:22px; - background:#f4f4f4; - border:none; - position: relative; - // DO I NEED THIS? - -webkit-box-shadow: none; - -moz-box-shadow: none; - box-shadow: none; -} + margin-bottom:22px; + background:#f4f4f4; + border:none; + position: relative; + box-shadow: none; -.subhead h1 { - font-family: 'Open Sans','Helvetica Neue', Helvetica, Arial, sans-serif; - font-size:26px; - font-weight:normal; - color:#777; - line-height: 55px; - margin-top: 10px; - margin-bottom: 10px; - span { - padding-right:10px; - } - small { - color:#999; - font-size:60%; - } -} + h1 { + font-family: 'Open Sans','Helvetica Neue', Helvetica, Arial, sans-serif; + font-size:26px; + font-weight:normal; + color:#777; + line-height: 55px; + margin-top: 10px; + margin-bottom: 10px; -.subhead h1.user { span { - padding-left:10px; - font-size: 24px; - line-height: 24px; - } - img { - width:42px; - height:42px; - border-radius:50%; - -webkit-border-radius:50%; - -moz-border-radius:50%; + padding-right:10px; } + small { - color:#777; + color:#999; font-size:60%; } + + &.user { + span { + padding-left:10px; + font-size: 24px; + line-height: 24px; + } + + img { + width:42px; + height:42px; + border-radius:50%; + } + + small { + color:#777; + font-size:60%; + } + } } -.subhead .container { - position:relative; - padding:0px; -} + .container { + position:relative; + padding:0; + } -// Give the tabs something to sit on -.subhead .nav-tabs { - //border-bottom: 1px solid #dee3e6; - position:absolute; - right:0px; - bottom:0px; - margin:0px; - padding:0px; - border-bottom:none; -} -// Make the list-items overlay the bottom border -.subhead .nav-tabs > li { - margin-bottom: 0px; -} -// Actual tabs (as links) -// Actual tabs (as links) -.subhead .nav-tabs > li > a { - color:#999; - padding:10px 20px; - line-height:20px; - border:0px; -} + // Give the tabs something to sit on + .nav-tabs { + //border-bottom: 1px solid #dee3e6; + position:absolute; + right:0; + bottom:0; + margin:0; + padding:0; + border-bottom:none; -.subhead .nav-tabs > li.active > a{ - color:#777; - border:0px; + // Make the list-items overlay the bottom border + > li { + margin-bottom: 0px; + + // Actual tabs (as links) + > a { + color:#999; + padding:10px 20px; + line-height:20px; + border:0; + } + + &.active > a { + color:#777; + border:0; + } + } + } } // // nav-repos // -------------------------------------------- -.nav-repos, -.nav-branches { - margin-top: 0px; - //margin-top:20px; - border:none; - li { - a { - line-height:22px; +.nav { + &-repos, &-branches { + margin-top: 0px; + //margin-top:20px; + border:none; - margin-bottom: 0px; - padding: 5px 15px; - padding:10px 15px; - border:none; - margin:0px !IMPORTANT; - font-size:15px; - border-radius:0px; - -moz-border-radius:0px; - -webkit-border-radius:0px; - overflow:hidden; - white-space:nowrap; - text-overflow:ellipsis; - span { - color:#777; + li { + a { + line-height:22px; + margin-bottom: 0px; + padding: 5px 15px; + padding:10px 15px; + border:none; + margin:0 !IMPORTANT; + font-size:15px; + border-radius:0; + overflow:hidden; + white-space:nowrap; + text-overflow:ellipsis; + + span { + color:#777; + + & [class^="icon-"] { + color:#999; + } + } + + i { + padding-right:10px; + color:#AAA; + font-size:18px; + vertical-align: middle; + } } - span [class^="icon-"] { - color:#999; + } + + li.active { + a { + background: #f4f4f4; } - i { - padding-right:10px; - color:#AAA; - font-size:18px; - vertical-align: middle; + + a:hover { + background: #f4f4f4; } } } - li.active { - a { - background: #f4f4f4; - } - a:hover { - background: #f4f4f4; - } + + &-repos &-header, &-branches &-header { + padding: 15px 15px; + margin-bottom: 0px; + color: #666; + font-size: 24px; + text-transform:lowercase; + font-family: 'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif; + font-weight: normal; + display:none; } -} -.nav-repos .nav-header, -.nav-branches .nav-header { - padding: 15px 15px; - margin-bottom: 0px; - color: #666; - font-size: 24px; - text-transform:lowercase; - font-family: 'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif; - font-weight: normal; - display:none; -} - -.nav-repos, -.nav-branches { - li { - a { + &-repos, &-branches { + li a { //border-top: 1px solid #EEE; padding: 12px 15px; - margin-bottom:0px; + margin-bottom:0; color:#777; } } -} -.nav-pills.nav-repos > li, -.nav-repos > li { - &> a { - padding:13px 15px; - } - &:nth-child(odd) { - background: #f7f7f7; - } -} -.nav-repos > li > a span { - color:#AAA; - &:last-child { - color:#555; - } -} + &-pills&-repos > li, &-repos > li { + > a { + padding:13px 15px; + } -.nav-pills.nav-branches > li > a { - padding:10px 15px; + &:nth-child(odd) { + background: #f7f7f7; + } + } + + &-repos > li > a span { + color:#AAA; + + &:last-child { + color:#555; + } + } + + &-pills&-branches > li > a { + padding:10px 15px; + } } // ALERTS @@ -287,81 +289,83 @@ body { padding: 30px; font-size: 18px; margin-bottom: 20px; + .pull-right { margin-top:-10px; } -} -.alert .thumbnails { - margin:0px; - padding:0px; - li { - margin:0px; - padding:0px; - margin-left:20px; - margin-top:20px; + .thumbnails { + margin:0; + padding:0; + + li { + margin:0; + padding:0; + margin-left:20px; + margin-top:20px; + } + + .thumbnail { + background:transparent; + } } - .thumbnail { - background:transparent; - } -} + &&-feed { + margin-top: 0px; + margin-bottom: 0px; + padding:15px 20px; -.alert.alert-feed { - margin-top: 0px; - margin-bottom: 0px; - padding:15px 20px; + span { + line-height:50px; + } - span { - line-height:50px; - } + span.label { + float:right; + color: #BBB; + font-size: 16px; + padding-right:10px; + font-weight:normal; + } - span.label { - float:right; - color: #BBB; - font-size: 16px; - padding-right:10px; - font-weight:normal; - } + .thumbnails > li { + margin:0; + margin-left:5px; + padding:0; + display:inline-block; - .thumbnails > li { - margin:0px; - margin-left:5px; - padding:0px; - display:inline-block; - a { - border: 0px; - padding: 0px; - img { - border-radius:50%; - -moz-border-radius:50%; - -webkit-border-radius:50%; - width: 32px; - height: 32px; - margin-top:10px; + a { + border: 0px; + padding: 0px; + + img { + border-radius:50%; + width: 32px; + height: 32px; + margin-top:10px; + } } } } -} -.alert.alert-success { - color: #3c763d; - background-color: #dff0d8; + &&-success { + color: #3c763d; + background-color: #dff0d8; - text-shadow: none; - font-size: 16px; - margin-top: 30px; - margin-bottom: 10px; -} + text-shadow: none; + font-size: 16px; + margin-top: 30px; + margin-bottom: 10px; + } -.alert.alert-error { - background: #ebccd1; - color: #a94442; + &&-error { + background: #ebccd1; + color: #a94442; - text-shadow: none; - font-size: 16px; - margin-top: 30px; - margin-bottom: 10px; + text-shadow: none; + font-size: 16px; + margin-top: 30px; + margin-bottom: 10px; + } } // @@ -369,319 +373,296 @@ body { // -------------------------------------------- .commit-list { - list-style: none; - margin:0px; - padding-left:0px; -} + list-style: none; + margin:0; + padding-left:0; + > li { + margin:0; + position:relative; + padding:25px 20px; + padding-left:25px; + list-style:none; + &> img { + position: absolute; + right: 20px; + top: 20px; + border-radius: 50%; + width: 48px; + height: 48px; + background: #EEE; + } -.commit-list > li { - margin:0px; - position:relative; - padding:25px 20px; - padding-left:25px; - list-style:none; + &:last-child { + border-bottom:none; + } - &> img { - position: absolute; - right: 20px; - top: 20px; - -webkit-border-radius: 50%; - -moz-border-radius: 50%; - border-radius: 50%; - width: 48px; - height: 48px; - background: #EEE; - } -} - -.commit-list > li:last-child { - border-bottom:none; -} - -.commit-list > li:nth-child(even) { - background:#f7f7f7; -} - -.commit-list.commit-list-alt > li:nth-child(even) { - background:#fff; -} - -.commit-list.commit-list-alt > li:nth-child(odd) { - background:#f7f7f7; -} - -.commit-list > li > h3 { - display:inline; - line-height:18px; - height:18px; - font-weight:normal; - color: #777; - font-size: 18px; - margin-top:10px; - a, span { - margin-top:2px; - display:inline-block; - } - a { - color:#555; - text-decoration:none; - &:hover { - text-decoration:underline; + &:nth-child(even) { + background:#f7f7f7; } } - small { - font-size:14px; - display:inline-block; - margin-left:5px; - white-space:nowrap; + + &.commit-list-alt > li { + &:nth-child(even) { + background:#fff; + } + + &:nth-child(odd) { + background:#f7f7f7; + } } - p { - font-size:14px; - color:#999; - margin:0px; - padding:0px; - padding-top:2px; - display: block; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - max-width: 350px; + + > li { + > h3 { + display:inline; + line-height:18px; + height:18px; + font-weight:normal; + color: #777; + font-size: 18px; + margin-top:10px; + + a, span { + margin-top:2px; + display:inline-block; + } + + a { + color:#555; + text-decoration:none; + + &:hover { + text-decoration:underline; + } + } + + small { + font-size:14px; + display:inline-block; + margin-left:5px; + white-space:nowrap; + } + + p { + font-size:14px; + color:#999; + margin:0; + padding:0; + padding-top:2px; + display: block; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + max-width: 350px; + } + } } } - // BUTTONS -.btn.btn-Success, -.btn.btn-Failure, -.btn.btn-Pending, -.btn.btn-Started, -.btn.btn-Error, -.btn.btn-None { +.btn { + &.btn-Success, + &.btn-Failure, + &.btn-Pending, + &.btn-Started, + &.btn-Error, + &.btn-None { - border: none; - background: #BBB; - color: #FFF; - font-weight: normal; - text-shadow: none; - box-shadow: none; - padding: 10px 15px; - font-size: 16px; - min-width:75px; - float:left; - margin-right:25px; - font-size:16px; - text-transform:none; - font-weight:normal; + border: none; + background: #BBB; + color: #FFF; + font-weight: normal; + text-shadow: none; + box-shadow: none; + padding: 10px 15px; + font-size: 16px; + min-width:75px; + float:left; + margin-right:25px; + font-size:16px; + text-transform:none; + font-weight:normal; - padding: 0px; - border-bottom: 0px; - width: 48px; - height: 48px; - min-height: 48px; - max-height: 48px; - min-width: 48px; - max-width: 48px; - border-radius:50%; - -webkit-border-radius:50%; - -moz-border-radius:50%; -} - -.btn.btn-success, -.btn.btn-Success { - background:rgba(81, 163, 81, 0.75); -} -.btn.btn-failure, -.btn.btn-Failure, -.btn.btn-Error { - background:rgba(189, 54, 47, 0.8); -} - -.btn.btn-Scheduled, -.btn.btn-Pending, -.btn.btn-Started { - background: #D5E802; -} - -.btn.btn-Success:before { - content: "\f00c"; - font-family: 'FontAwesome'; - font-size: 22px; - line-height: 48px; - opacity:0.8; - color:#FFF; -} -.btn.btn-Error:before, -.btn.btn-Failure:before { - content: "\f00d"; - font-family: 'FontAwesome'; - font-size: 22px; - line-height: 48px; - opacity:0.8; - color:#fff; -} - -.btn.btn-refresh { - position: absolute; - left: -95px; - float: left; - background: #f7f7f7; - width: 75px; - border-radius: 0px; - -webkit-border-radius: 0px; - -moz-border-radius: 0px; - text-align: center; - font-size: 22px; - text-decoration: none; - color: #999; - padding:10px 0px; - z-index:1; - span { - display:block; - font-size:14px; - padding-top:5px; + padding: 0px; + border-bottom: 0px; + width: 48px; + height: 48px; + min-height: 48px; + max-height: 48px; + min-width: 48px; + max-width: 48px; + border-radius:50%; } -} -.btn.btn-Started:before, -.btn.btn-Scheduled:before, -.btn.btn-Pending:before { - content: "\f021"; - font-family: 'FontAwesome'; - font-size: 22px; - line-height: 48px; - color:#FFF; + &.btn-success, + &.btn-Success { + background:rgba(81, 163, 81, 0.75); + } - display: inline-block; + &.btn-failure, + &.btn-Failure, + &.btn-Error { + background:rgba(189, 54, 47, 0.8); + } - -webkit-animation: spin 1.5s infinite linear; - -moz-animation: spin 1.5s infinite linear; - -ms-animation: spin 1.5s infinite linear; - -o-animation: spin 1.5s infinite linear; - animation: spin 1.5s infinite linear; -} + &.btn-Scheduled, + &.btn-Pending, + &.btn-Started { + background: #D5E802; + } -.btn.btn-mini { - width:24px; - height:24px; - max-width:24px; - max-height:24px; - min-width:24px; - min-height:24px; -} + &.btn-Success:before { + content: "\f00c"; + font-family: 'FontAwesome'; + font-size: 22px; + line-height: 48px; + opacity:0.8; + color:#FFF; + } -.btn.btn-mini.btn-Success:before, -.btn.btn-mini.btn-Failure:before, -.btn.btn-mini.btn-Error:before, -.btn.btn-mini.btn-Started:before, -.btn.btn-mini.btn-Scheduled:before, -.btn.btn-mini.btn-Pending:before { - line-height:24px !IMPORTANT; - font-size:14px !IMPORTANT; -} + &.btn-Error:before, + &.btn-Failure:before { + content: "\f00d"; + font-family: 'FontAwesome'; + font-size: 22px; + line-height: 48px; + opacity:0.8; + color:#fff; + } -@-webkit-keyframes spin { - to { -webkit-transform: rotate(360deg); } -} + &.btn-refresh { + position: absolute; + left: -95px; + float: left; + background: #f7f7f7; + width: 75px; + border-radius: 0px; + text-align: center; + font-size: 22px; + text-decoration: none; + color: #999; + padding:10px 0px; + z-index:1; -@-moz-keyframes spin { - to { -moz-transform: rotate(360deg); } -} + span { + display:block; + font-size:14px; + padding-top:5px; + } + } -@-ms-keyframes spin { - to { -ms-transform: rotate(360deg); } -} + &.btn-Started:before, + &.btn-Scheduled:before, + &.btn-Pending:before { + content: "\f021"; + font-family: 'FontAwesome'; + font-size: 22px; + line-height: 48px; + color:#FFF; + display: inline-block; + animation: spin 1.5s infinite linear; + } -@-o-keyframes spin { - to { -o-transform: rotate(360deg); } + &.btn-mini { + width:24px; + height:24px; + max-width:24px; + max-height:24px; + min-width:24px; + min-height:24px; + + &.btn-Success:before, + &.btn-Failure:before, + &.btn-Error:before, + &.btn-Started:before, + &.btn-Scheduled:before, + &.btn-Pending:before { + line-height:24px !IMPORTANT; + font-size:14px !IMPORTANT; + } + } + + &.btn-None { + //background: #ebebeb; + //border-bottom: 2px solid #CCC; + //color: #737373; + background:rgba(81, 163, 81, 0.75); + border-bottom:2px solid rgba(81, 163, 81, 1); + } + + &.btn-default { + padding:7px 20px; + padding-bottom:6px; + border-bottom-width: 2px; + color:#777; + } + + &.btn-primary { + background:rgba(251, 136, 35, 0.85); + padding:7px 20px; + border:none; + border-bottom: 2px solid #cd6d1d; + + &:hover, &:active { + background:rgba(251, 136, 35, 1); + border-bottom: 2px solid #cd6d1d; + } + } + + &.btn-danger { + border:none; + padding:7px 20px; + background: rgba(189, 54, 47, 0.8); + border-bottom: 2px solid #bd362f; + + &:hover, &:active { + background:rgba(189, 54, 47, 0.9); + border-bottom: 2px solid #bd362f; + } + } } @keyframes spin { - to { transform: rotate(360deg); } -} - - -.btn.btn-None { - //background: #ebebeb; - //border-bottom: 2px solid #CCC; - //color: #737373; - background:rgba(81, 163, 81, 0.75); - border-bottom:2px solid rgba(81, 163, 81, 1); -} - - -.btn.btn-default { - padding:7px 20px; - padding-bottom:6px; - border-bottom-width: 2px; - color:#777; -} - -.btn.btn-primary, { - background:rgba(251, 136, 35, 0.85); - padding:7px 20px; - border:none; - border-bottom: 2px solid #cd6d1d; - &:hover, &:active { - background:rgba(251, 136, 35, 1); - border-bottom: 2px solid #cd6d1d; - } -} - -.btn.btn-danger { - border:none; - padding:7px 20px; - background: rgba(189, 54, 47, 0.8); - border-bottom: 2px solid #bd362f; - &:hover, &:active { - background:rgba(189, 54, 47, 0.9); - border-bottom: 2px solid #bd362f; - } + to { transform: rotate(360deg); } } /////////////////////////////////////////////////////// /////////////////////////////////////////////////////// // NAV PILLS -.nav-pills > li { - //margin-top: 2px; - a { - padding:7px 15px; - color: rgb(119, 119, 119); +.nav-pills { + > li { + //margin-top: 2px; + a { + padding:7px 15px; + color: rgb(119, 119, 119); + } + + > a, > a:hover, > a:focus { + border-radius:0; + //margin-top:0; + //padding:7px 15px; + } + } + + > .active { + border-radius:0; + //margin-top:0; } } -.nav-pills > li > a, -.nav-pills > li > a:hover, -.nav-pills > li > a:focus{ - border-radius:0px; - -webkit-border-radius:0px; - -moz-border-radius:0px; - //margin-top:0px; - //padding:7px 15px; -} - -.nav-pills > .active { - border-radius:0px; - -webkit-border-radius:0px; - -moz-border-radius:0px; - //margin-top:0px; -} - .nav-pills>li.active>a, .nav-pills>li.active>a:hover, .nav-pills>li.active>a:focus, .nav-pills > .active > a, .nav-pills > .active > a:hover, .nav-pills > .active > a:focus{ - border-radius:0px; - -webkit-border-radius:0px; - -moz-border-radius:0px; - //margin-bottom:0px; - margin-top:0px; + border-radius:0; + //margin-bottom:0; + margin-top:0; background-color: #f4f4f4; color: rgb(119, 119, 119); @@ -690,33 +671,35 @@ body { ////////////////////////////////////// // FORM -form label { - color: #777; - margin-top: 25px; - margin-bottom: 7px; - margin-left: 2px; - cursor:default; - font-weight:normal; - display:block; +form { + label { + color: #777; + margin-top: 25px; + margin-bottom: 7px; + margin-left: 2px; + cursor:default; + font-weight:normal; + display:block; - &:first-child { - margin-top:0px; + &:first-child { + margin-top:0; + } } -} -form .form-actions { - background:transparent; - padding:30px 0px; - margin:0px; - border:none; + .form-actions { + background:transparent; + padding:30px 0px; + margin:0; + border:none; - .btn { - margin-right:3px; + .btn { + margin-right:3px; + } } -} -form .form-group { - margin-bottom:30px; + .form-group { + margin-bottom:30px; + } } input.form-control, @@ -728,41 +711,36 @@ select.form-control { font-size: 14px; line-height: 20px; color: #555555; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; border-radius: 4px; vertical-align: middle; width:270px; } -input.form-control-small, -select.form-control-small { - width: 90px; -} +input, select { + &.form-control-small { + width: 90px; + } -input.form-control-large, -select.form-control-large { - width: 210px; -} + &.form-control-large { + width: 210px; + } -input.form-control-xlarge, -select.form-control-xlarge { - width: 270px; + &.form-control-xlarge { + width: 270px; + } } span.form-control { color: #999999; background-color: #fcfcfc; border-color: #cccccc; - -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); - -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); - box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); - cursor: not-allowed; + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); + cursor: not-allowed; } textarea { - font-family: 'Droid Sans Mono', monospace; + font-family: 'Droid Sans Mono', monospace; } /////////////////////////////// @@ -775,12 +753,14 @@ textarea { background:#eee; padding:5px; text-decoration:none; + span { display: block; text-align: center; color: #777; padding: 5px; } + img { width:160px; height:160px; @@ -789,8 +769,10 @@ textarea { min-width:160px; min-height:160px; } + &:hover { text-decoration:none; + &:before { color: #fff; text-shadow: 1px 1px rgba(0,0,0,0.5); @@ -807,27 +789,30 @@ textarea { /////////////////////////// .row.gravatar-list { - margin-left:0px; + margin-left:0; + .col-xs-3 { width:23.404255319148934%; - margin:0px; - padding:0px; + margin:0; + padding:0; margin-right:2.127659574468085%; + &:nth-child(4) { - margin-right:0px; + margin-right:0; } + a.thumbnail, a.thumbnail:hover { - border-radius:0px; - -webkit-border-radius:0px; - -moz-border-radius:0px; + border-radius:0; box-shadow:none; border:none; text-decoration:none; background:#f4f4f4; + img { width:156px; height:156px; } + .caption > h3 { font-weight:normal; text-align:center; @@ -841,8 +826,9 @@ textarea { text-overflow: ellipsis; color:rgb(85, 85, 85); } + &.team-add { - &> span { + > span { min-width:156px; min-height:156px; display:inline-block; @@ -852,6 +838,7 @@ textarea { font-size: 64px; color: #d7d7d7; } + .caption > h3 { color: #999; } @@ -860,130 +847,121 @@ textarea { } } - - - - - - //////////////////////////////////////// // SWITCH -/* Checkbox hack */ -.switch input, -.switch a { - cursor: pointer; - height: 100%; - opacity: 0; - position: absolute; - width: 100%; - z-index: 100; -} - -/* Container */ +// Container .switch { height: 40px; position: relative; width: 100px; + + // Checkbox hack + input, a { + cursor: pointer; + height: 100%; + opacity: 0; + position: absolute; + width: 100%; + z-index: 100; + } + + // Background unchecked + label { + background: rgba(189, 54, 47, 0.8); + border-radius: 2px; + display: block; + height: 100%; + position: relative; + transition: all .15s ease; + width: 100%; + border-radius:5px; + color:#FFF; + + // Slider unchecked + div { + background: #fff; + border-radius: 2px; + display: block; + height: 28px; + left: 6px; + position: absolute; + top: 6px; + transition: all .15s ease; + width: 35px; + z-index: 3; + border-radius:5px; + + // Vertical lines on slider + &:before { + bottom: 0; + content: ''; + display: block; + height: 15px; + left: 0; + margin: auto; + position: absolute; + right: 0; + top: 0; + transition: all .15s ease; + width: 3px; + //box-shadow: -6px 0 0 0 #d3d3d3, 6px 0 0 0 #d3d3d3; + } + } + } + + // Icon styles + .fontawesome-ok { + font-size: 18px; + left: 15px; + position: relative; + top: 8px; + z-index: 2; + + &:before { + font-family: 'FontAwesome'; + content:"\f00c"; + opacity:0.8; + } + } + + .fontawesome-remove { + font-size: 18px; + left: 45px; + position: relative; + top: 8px; + z-index: 2; + + &:before { + font-family: 'FontAwesome'; + content:"\f00d"; + opacity:0.8; + } + } + + // Checked States + input:checked~label { + background: rgba(81, 163, 81, 0.75); + + div { + left: 58px; + } + } } -/* Background unchecked */ -.switch label { - background: rgba(189, 54, 47, 0.8); - border-radius: 2px; - display: block; - height: 100%; - position: relative; - transition: all .15s ease; - width: 100%; - border-radius:5px; - color:#FFF; -} - -/* Slider unchecked */ -.switch label div { - background: #fff; - border-radius: 2px; - display: block; - height: 28px; - left: 6px; - position: absolute; - top: 6px; - transition: all .15s ease; - width: 35px; - z-index: 3; - border-radius:5px; -} - -/* Vertical lines on slider */ -.switch label div:before { - bottom: 0; - content: ''; - display: block; - height: 15px; - left: 0; - margin: auto; - position: absolute; - right: 0; - top: 0; - transition: all .15s ease; - width: 3px; - //box-shadow: -6px 0 0 0 #d3d3d3, 6px 0 0 0 #d3d3d3; -} - -/* Icon styles */ -.switch .fontawesome-ok { - font-size: 18px; - left: 15px; - position: relative; - top: 8px; - z-index: 2; -} - -.switch .fontawesome-ok:before { - font-family: 'FontAwesome'; - content:"\f00c"; - opacity:0.8; -} - - -.switch .fontawesome-remove { - font-size: 18px; - left: 45px; - position: relative; - top: 8px; - z-index: 2; -} - -.switch .fontawesome-remove:before { - font-family: 'FontAwesome'; - content:"\f00d"; - opacity:0.8; -} - -/* Checked States */ -.switch input:checked~label { - background: rgba(81, 163, 81, 0.75); -} - -.switch input:checked~label div { - left: 58px; -} - - /////// BUID OUTPUT pre { - white-space: pre-wrap; - background: #464849; - color: #FFF; - padding: 20px 20px; - border-radius: 5px; - font-size: 12px; - margin-top: 0px; - overflow: hidden; - font-family:'Droid Sans Mono', 'monospace'; - line-height: 20px + white-space: pre-wrap; + background: #464849; + color: #FFF; + padding: 20px 20px; + border-radius: 5px; + font-size: 12px; + margin-top: 0px; + overflow: hidden; + font-family:'Droid Sans Mono', 'monospace'; + line-height: 20px } @@ -992,301 +970,292 @@ pre { .alert.alert-build-Failure, .alert.alert-build-Pending, .alert.alert-build-Started { - text-shadow:none; - margin-top:5px; - margin-bottom:30px; - font-size:15px; - padding:15px 20px; - border-radius:5px; - -webkit-border-radius:5px; - -moz-border-radius:5px; + text-shadow:none; + margin-top:5px; + margin-bottom:30px; + font-size:15px; + padding:15px 20px; + border-radius:5px; - span { - line-height:32px; - span { - text-decoration:underline; - } - } + span { + line-height:32px; - a.btn { - width:32px; - height:32px; - max-width:32px; - max-height:32px; - min-width:32px; - min-height:32px; - margin-right:20px !IMPORTANT; - &:before { - font-size: 22px !IMPORTANT; - line-height: 32px !IMPORTANT; - } - } + span { + text-decoration:underline; + } + } - .actions { - float: right; - margin-top: -2px; - } + a.btn { + width:32px; + height:32px; + max-width:32px; + max-height:32px; + min-width:32px; + min-height:32px; + margin-right:20px !IMPORTANT; + + &:before { + font-size: 22px !IMPORTANT; + line-height: 32px !IMPORTANT; + } + } + + .actions { + float: right; + margin-top: -2px; + } } .build-details { + dt { + float:left; + width:90px; + color:#333; + font-weight:normal; - dt { - float:left; - width:90px; - color:#333; - font-weight:normal; - &:after { - content:':'; - } - } - dd { - color:#555; - white-space:nowrap; - } + &:after { + content:':'; + } + } - .build-summary { - float:left; - width:300px; - padding-left:20px; - } + dd { + color:#555; + white-space:nowrap; + } - img { - float:left; - -webkit-border-radius:50%; - -moz-border-radius:50%; - border-radius:50%; - margin-right: 30px; - width: 58px; - height: 58px; - } + .build-summary { + float:left; + width:300px; + padding-left:20px; + } - .commit-summary { - float:left; - width: 500px; - - dd { - overflow: hidden; - text-overflow: ellipsis; - max-width: 450px; - } - } - - background:#FFF; - margin-bottom:40px; + img { + float:left; + border-radius:50%; + margin-right: 30px; + width: 58px; + height: 58px; + } + .commit-summary { + float:left; + width: 500px; + dd { + overflow: hidden; + text-overflow: ellipsis; + max-width: 450px; + } + } + background:#FFF; + margin-bottom:40px; } .build-details.affix { - top: 0px; - padding-top:15px; - padding-bottom:30px; + top: 0px; + padding-top:15px; + padding-bottom:30px; } + .build-details.affix-top { } .alert.alert-build-Success { - color: #468847; - background-color: #dff0d8; - border-color: #d6e9c6; + color: #468847; + background-color: #dff0d8; + border-color: #d6e9c6; } .alert.alert-build-Error, .alert.alert-build-Failure { - background-color:#f2dede; - color:#b94a48; + background-color:#f2dede; + color:#b94a48; } .alert.alert-build-Pending, .alert.alert-build-Started { - color: #c09853; - background: rgba(213, 232, 2, 0.2); - background-color: rgba(213, 232, 2, 0.2); + color: #c09853; + background: rgba(213, 232, 2, 0.2); + background-color: rgba(213, 232, 2, 0.2); } - - - - - // new repo form .form-repo { - .field-group { - display:inline-block; - margin-bottom:30px; - label { - margin-top:0px; - } - } - .field-separator { - display:inline-block; - font-size:20px; - } + .field-group { + display:inline-block; + margin-bottom:30px; - ul { - padding-top:15px; - margin-bottom:0px; - padding-bottom:0px; - list-style: none; - padding-left:0px; - margin-left:0px; - li { - padding-bottom:10px; - img { - width:32px; - height:32px; - border-radius:50%; - -webkit-border-radius:50%; - -moz-border-radius:50%; - margin-left:10px; - margin-right:10px; - } - } - } + label { + margin-top:0; + } + } + + .field-separator { + display:inline-block; + font-size:20px; + } + + ul { + padding-top:15px; + margin-bottom:0; + padding-bottom:0; + list-style: none; + padding-left:0; + margin-left:0; + + li { + padding-bottom:10px; + + img { + width:32px; + height:32px; + border-radius:50%; + margin-left:10px; + margin-right:10px; + } + } + } } - ul.account-radio-group { - padding-top:15px; - margin-bottom:0px; - padding-bottom:0px; - list-style: none; - padding-left:0px; - margin-left:0px; - li { - padding-bottom:10px; - img { - width:32px; - height:32px; - border-radius:50%; - -webkit-border-radius:50%; - -moz-border-radius:50%; - margin-left:10px; - margin-right:10px; - } - } - } + ul.account-radio-group { + padding-top:15px; + margin-bottom:0; + padding-bottom:0; + list-style: none; + padding-left:0; + margin-left:0; + + li { + padding-bottom:10px; + + img { + width:32px; + height:32px; + border-radius:50%; + margin-left:10px; + margin-right:10px; + } + } + } // LOGIN FORM .form-centered { - background:#FFF; - overflow:hidden; - max-width: 350px; - margin: 0 auto; - text-align: center; - margin-top: 40px; - padding: 0px 0px 20px 0px; - border-radius:5px; - -webkit-border-radius:5px; - -moz-border-radius:5px; + background:#FFF; + overflow:hidden; + max-width: 350px; + margin: 0 auto; + text-align: center; + margin-top: 40px; + padding: 0px 0px 20px 0px; + border-radius:5px; - h1 { - background-color: #262829; - color: #FFF; - border-top-left-radius: 5px; - border-top-right-radius: 5px; - font-family: 'Orbitron'; - font-weight: normal; - font-size: 18px; - padding: 25px 10px; - margin-top:0px; - margin-bottom:30px; - } + h1 { + background-color: #262829; + color: #FFF; + border-top-left-radius: 5px; + border-top-right-radius: 5px; + font-family: 'Orbitron'; + font-weight: normal; + font-size: 18px; + padding: 25px 10px; + margin-top:0; + margin-bottom:30px; + } - input[type="submit"] { + input[type="submit"] { - border:none; - color: #FFF; - padding: 10px; - font-size: 18px; - margin-top: 30px; - margin-bottom: 10px; - width: 280px; - background: #AAA; - border-radius:5px; - -webkit-border-radius:5px; - -moz-border-radius:5px; - } + border:none; + color: #FFF; + padding: 10px; + font-size: 18px; + margin-top: 30px; + margin-bottom: 10px; + width: 280px; + background: #AAA; + border-radius:5px; + } - input[type="text"], - input[type="email"], - input[type="password"] { - margin-bottom: 0px; - font-size: 16px; - padding: 10px; - width: 280px; - height:40px; - &:last-child { - border-top-width: 0px; - border-top-right-radius: 0px; - border-top-left-radius: 0px; - } - &:first-child { - border-bottom-right-radius: 0px; - border-bottom-left-radius: 0px; - } - &[disabled] { - box-shadow: none; - background: #f7f7f7; - color: #999; - text-shadow: none; - } - &.only-child { - border-bottom-right-radius: 4px; - border-bottom-left-radius: 4px; - border-top-right-radius: 4px; - border-top-left-radius: 4px; - border-bottom-width: 1px; - border-top-width: 1px; - } - } + input[type="text"], + input[type="email"], + input[type="password"] { + margin-bottom: 0px; + font-size: 16px; + padding: 10px; + width: 280px; + height:40px; + &:last-child { + border-top-width: 0px; + border-top-right-radius: 0px; + border-top-left-radius: 0px; + } + &:first-child { + border-bottom-right-radius: 0px; + border-bottom-left-radius: 0px; + } + &[disabled] { + box-shadow: none; + background: #f7f7f7; + color: #999; + text-shadow: none; + } + &.only-child { + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; + border-top-right-radius: 4px; + border-top-left-radius: 4px; + border-bottom-width: 1px; + border-top-width: 1px; + } + } - a, a:visited { - color:#999; - text-decoration:underline; - } + a, a:visited { + color:#999; + text-decoration:underline; + } - h1~div.alert { - margin-top:-30px; - } + h1~div.alert { + margin-top:-30px; + } - .alert.alert-danger { - background: #ebccd1; - color: #a94442; - text-shadow: none; - font-weight: normal; - font-size: 16px; - padding: 20px; - margin-bottom:30px; - } + .alert.alert-danger { + background: #ebccd1; + color: #a94442; + text-shadow: none; + font-weight: normal; + font-size: 16px; + padding: 20px; + margin-bottom:30px; + } - .alert.alert-success { - color: #3c763d; - background-color: #dff0d8; - text-shadow: none; - font-weight: normal; - font-size: 16px; - padding: 20px; - margin-bottom:30px; - } + .alert.alert-success { + color: #3c763d; + background-color: #dff0d8; + text-shadow: none; + font-weight: normal; + font-size: 16px; + padding: 20px; + margin-bottom:30px; + } } .url { - word-break: break-all; + word-break: break-all; } #follow { - position: relative; - z-index: 100; - float: right; - right: 1em; - top: -2.5em; - padding: 0 1em; - border-radius: 7px; - background: #999; - cursor: pointer; + position: relative; + z-index: 100; + float: right; + right: 1em; + top: -2.5em; + padding: 0 1em; + border-radius: 7px; + background: #999; + cursor: pointer; } + From c6930ef57b56f35bc620ae05f9b8a5e57f9d5b7d Mon Sep 17 00:00:00 2001 From: Martin Charles Date: Mon, 2 Jun 2014 12:44:27 -0400 Subject: [PATCH 7/8] Fixed an Unhandled Error in badges.go There was an unhandled error in badges.go. If you pass an invalid branch to the badge handler (http://beta.drone.io/github.com/drone/drone/ status.svg?branch=this) the browser is told to redirect to a blank string. This is unintended behavior. Now, a 404 response is sent instead. --- pkg/handler/badges.go | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/pkg/handler/badges.go b/pkg/handler/badges.go index 3a98a3fcf..1994d6e5a 100644 --- a/pkg/handler/badges.go +++ b/pkg/handler/badges.go @@ -43,26 +43,29 @@ func Badge(w http.ResponseWriter, r *http.Request) error { // get the latest commit from the database // for the requested branch commit, err := database.GetBranch(repo.ID, branchParam) - if err == nil { - switch { - case commit.Status == "Success" && len(successParam) == 0: - // if no success image is provided, we serve a - // badge using the shields.io service - badge = badgeSuccess - case commit.Status == "Success" && len(successParam) != 0: - // otherwise we serve the user defined success badge - badge = successParam - case commit.Status == "Failure" && len(failureParam) == 0: - // if no failure image is provided, we serve a - // badge using the shields.io service - badge = badgeFailure - case commit.Status == "Failure" && len(failureParam) != 0: - // otherwise we serve the user defined failure badge - badge = failureParam - default: - // otherwise load unknown image - badge = badgeUnknown - } + if err != nil { + http.NotFound(w, r) + return nil + } + + switch { + case commit.Status == "Success" && len(successParam) == 0: + // if no success image is provided, we serve a + // badge using the shields.io service + badge = badgeSuccess + case commit.Status == "Success" && len(successParam) != 0: + // otherwise we serve the user defined success badge + badge = successParam + case commit.Status == "Failure" && len(failureParam) == 0: + // if no failure image is provided, we serve a + // badge using the shields.io service + badge = badgeFailure + case commit.Status == "Failure" && len(failureParam) != 0: + // otherwise we serve the user defined failure badge + badge = failureParam + default: + // otherwise load unknown image + badge = badgeUnknown } http.Redirect(w, r, badge, http.StatusSeeOther) From 2d1e25cfb96645b440fb5d88fdf25c2222b2ea9f Mon Sep 17 00:00:00 2001 From: Justin Phelps Date: Mon, 2 Jun 2014 15:14:44 -0500 Subject: [PATCH 8/8] serce => serve --- cmd/droned/drone.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/droned/drone.go b/cmd/droned/drone.go index b5641af8c..8037af4ab 100644 --- a/cmd/droned/drone.go +++ b/cmd/droned/drone.go @@ -122,7 +122,7 @@ func setupStatic() { w.Header().Add("Cache-Control", "no-cache") } - // serce images + // serve images images.ServeHTTP(w, r) }) }