diff --git a/.drone.yml b/.drone.yml index 6dbe2be95..b00d04a34 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,23 +1,21 @@ workspace: - base: /drone + base: /go path: src/github.com/drone/drone pipeline: - test: - image: drone/golang:1.5 + backend: + image: golang:1.6 environment: - GO15VENDOREXPERIMENT=1 - - GOPATH=/drone commands: - - export PATH=$PATH:$GOPATH/bin - make deps gen - make test test_postgres test_mysql - build: - image: drone/golang:1.5 + compile: + image: golang:1.6 environment: - GO15VENDOREXPERIMENT=1 - - GOPATH=/drone + - GOPATH=/go commands: - export PATH=$PATH:$GOPATH/bin - make build diff --git a/.drone.yml.sig b/.drone.yml.sig index 3b267ac3e..4f1196374 100644 --- a/.drone.yml.sig +++ b/.drone.yml.sig @@ -1 +1 @@ -eyJhbGciOiJIUzI1NiJ9.d29ya3NwYWNlOgogIGJhc2U6IC9kcm9uZQogIHBhdGg6IHNyYy9naXRodWIuY29tL2Ryb25lL2Ryb25lCgpwaXBlbGluZToKICB0ZXN0OgogICAgaW1hZ2U6IGRyb25lL2dvbGFuZzoxLjUKICAgIGVudmlyb25tZW50OgogICAgICAtIEdPMTVWRU5ET1JFWFBFUklNRU5UPTEKICAgICAgLSBHT1BBVEg9L2Ryb25lCiAgICBjb21tYW5kczoKICAgICAgLSBleHBvcnQgUEFUSD0kUEFUSDokR09QQVRIL2JpbgogICAgICAtIG1ha2UgZGVwcyBnZW4KICAgICAgLSBtYWtlIHRlc3QgdGVzdF9wb3N0Z3JlcyB0ZXN0X215c3FsCgogIGJ1aWxkOgogICAgaW1hZ2U6IGRyb25lL2dvbGFuZzoxLjUKICAgIGVudmlyb25tZW50OgogICAgICAtIEdPMTVWRU5ET1JFWFBFUklNRU5UPTEKICAgICAgLSBHT1BBVEg9L2Ryb25lCiAgICBjb21tYW5kczoKICAgICAgLSBleHBvcnQgUEFUSD0kUEFUSDokR09QQVRIL2JpbgogICAgICAtIG1ha2UgYnVpbGQKICAgIHdoZW46CiAgICAgIGV2ZW50OiBwdXNoCgogIHB1Ymxpc2g6CiAgICBpbWFnZTogczMKICAgIGFjbDogcHVibGljLXJlYWQKICAgIGJ1Y2tldDogZG93bmxvYWRzLmRyb25lLmlvCiAgICBzb3VyY2U6IHJlbGVhc2UvKiovKi4qCiAgICB3aGVuOgogICAgICBldmVudDogcHVzaAogICAgICBicmFuY2g6IG1hc3RlcgoKICBkb2NrZXI6CiAgICByZXBvOiBkcm9uZS9kcm9uZQogICAgdGFnOiBbICIwLjUuMCIsICIwLjUiIF0KICAgIHN0b3JhZ2VfZHJpdmVyOiBvdmVybGF5CiAgICB3aGVuOgogICAgICBicmFuY2g6IG1hc3RlcgogICAgICBldmVudDogcHVzaAoKc2VydmljZXM6CiAgcG9zdGdyZXM6CiAgICBpbWFnZTogcG9zdGdyZXM6OS40LjUKICAgIGVudmlyb25tZW50OgogICAgICAtIFBPU1RHUkVTX1VTRVI9cG9zdGdyZXMKICBteXNxbDoKICAgIGltYWdlOiBteXNxbDo1LjYuMjcKICAgIGVudmlyb25tZW50OgogICAgICAtIE1ZU1FMX0RBVEFCQVNFPXRlc3QKICAgICAgLSBNWVNRTF9BTExPV19FTVBUWV9QQVNTV09SRD15ZXMK.PFyNsvPRNRtL_9Tlgsqa0IDMIgzUrlk53eV4QJjZ3hU \ No newline at end of file +eyJhbGciOiJIUzI1NiJ9.d29ya3NwYWNlOgogIGJhc2U6IC9nbwogIHBhdGg6IHNyYy9naXRodWIuY29tL2Ryb25lL2Ryb25lCgpwaXBlbGluZToKICBiYWNrZW5kOgogICAgaW1hZ2U6IGdvbGFuZzoxLjYKICAgIGVudmlyb25tZW50OgogICAgICAtIEdPMTVWRU5ET1JFWFBFUklNRU5UPTEKICAgIGNvbW1hbmRzOgogICAgICAtIG1ha2UgZGVwcyBnZW4KICAgICAgLSBtYWtlIHRlc3QgdGVzdF9wb3N0Z3JlcyB0ZXN0X215c3FsCgogIGNvbXBpbGU6CiAgICBpbWFnZTogZ29sYW5nOjEuNgogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gR08xNVZFTkRPUkVYUEVSSU1FTlQ9MQogICAgICAtIEdPUEFUSD0vZ28KICAgIGNvbW1hbmRzOgogICAgICAtIGV4cG9ydCBQQVRIPSRQQVRIOiRHT1BBVEgvYmluCiAgICAgIC0gbWFrZSBidWlsZAogICAgd2hlbjoKICAgICAgZXZlbnQ6IHB1c2gKCiAgcHVibGlzaDoKICAgIGltYWdlOiBzMwogICAgYWNsOiBwdWJsaWMtcmVhZAogICAgYnVja2V0OiBkb3dubG9hZHMuZHJvbmUuaW8KICAgIHNvdXJjZTogcmVsZWFzZS8qKi8qLioKICAgIHdoZW46CiAgICAgIGV2ZW50OiBwdXNoCiAgICAgIGJyYW5jaDogbWFzdGVyCgogIGRvY2tlcjoKICAgIHJlcG86IGRyb25lL2Ryb25lCiAgICB0YWc6IFsgIjAuNS4wIiwgIjAuNSIgXQogICAgc3RvcmFnZV9kcml2ZXI6IG92ZXJsYXkKICAgIHdoZW46CiAgICAgIGJyYW5jaDogbWFzdGVyCiAgICAgIGV2ZW50OiBwdXNoCgpzZXJ2aWNlczoKICBwb3N0Z3JlczoKICAgIGltYWdlOiBwb3N0Z3Jlczo5LjQuNQogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUE9TVEdSRVNfVVNFUj1wb3N0Z3JlcwogIG15c3FsOgogICAgaW1hZ2U6IG15c3FsOjUuNi4yNwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gTVlTUUxfREFUQUJBU0U9dGVzdAogICAgICAtIE1ZU1FMX0FMTE9XX0VNUFRZX1BBU1NXT1JEPXllcwo.kQIwqIgs7PnoKIGmzJ6hlbWTbV5zK0w4HVWsux79P3s \ No newline at end of file diff --git a/.gitignore b/.gitignore index 229b1e184..0166e230d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ drone/drone *.sqlite *_gen.go -*.html #*.css *.txt *.zip @@ -13,6 +12,8 @@ drone/drone temp/ release/ +server/frontend/bower_components +server/frontend/build server/swagger/files/*.json # vendored repositories that we don't actually need diff --git a/Makefile b/Makefile index 41282b7b7..6ae874ea5 100644 --- a/Makefile +++ b/Makefile @@ -10,22 +10,20 @@ endif all: gen build_static -deps: +deps: deps_backend deps_frontend + +deps_frontend: + go get -u github.com/drone/drone-ui/dist + +deps_backend: go get -u golang.org/x/tools/cmd/cover - go get -u github.com/eknkc/amber/... - go get -u github.com/eknkc/amber go get -u github.com/jteeuwen/go-bindata/... go get -u github.com/elazarl/go-bindata-assetfs/... - go get -u github.com/dchest/jsmin - go get -u github.com/franela/goblin -gen: gen_static gen_template gen_migrations - -gen_static: - go generate github.com/drone/drone/static +gen: gen_template gen_migrations gen_template: - go generate github.com/drone/drone/template + go generate github.com/drone/drone/server/template gen_migrations: go generate github.com/drone/drone/store/datastore/ddl diff --git a/cache/helper.go b/cache/helper.go index abf1d91c9..f4def328e 100644 --- a/cache/helper.go +++ b/cache/helper.go @@ -53,6 +53,20 @@ func GetRepos(c context.Context, user *model.User) ([]*model.RepoLite, error) { return repos, nil } +// GetRepoMap returns the list of user repositories from the cache +// associated with the current context in a map structure. +func GetRepoMap(c context.Context, user *model.User) (map[string]bool, error) { + repos, err := GetRepos(c, user) + if err != nil { + return nil, err + } + repom := map[string]bool{} + for _, repo := range repos { + repom[repo.FullName] = true + } + return repom, nil +} + // DeleteRepos evicts the cached user repositories from the cache associated // with the current context. func DeleteRepos(c context.Context, user *model.User) error { diff --git a/client/client.go b/client/client.go index bf1ea1553..c1f4834bd 100644 --- a/client/client.go +++ b/client/client.go @@ -43,6 +43,9 @@ type Client interface { // RepoPatch updates a repository. RepoPatch(*model.Repo) (*model.Repo, error) + // RepoChown updates a repository owner. + RepoChown(string, string) (*model.Repo, error) + // RepoDel deletes a repository. RepoDel(string, string) error diff --git a/client/client_impl.go b/client/client_impl.go index 7fa3099c7..ac6b42513 100644 --- a/client/client_impl.go +++ b/client/client_impl.go @@ -32,6 +32,7 @@ const ( pathFeed = "%s/api/user/feed" pathRepos = "%s/api/user/repos" pathRepo = "%s/api/repos/%s/%s" + pathChown = "%s/api/repos/%s/%s/chown" pathEncrypt = "%s/api/repos/%s/%s/encrypt" pathBuilds = "%s/api/repos/%s/%s/builds" pathBuild = "%s/api/repos/%s/%s/builds/%v" @@ -153,6 +154,14 @@ func (c *client) RepoPost(owner string, name string) (*model.Repo, error) { return out, err } +// RepoChow updates a repository owner. +func (c *client) RepoChown(owner string, name string) (*model.Repo, error) { + out := new(model.Repo) + uri := fmt.Sprintf(pathChown, c.base, owner, name) + err := c.post(uri, nil, out) + return out, err +} + // RepoPatch updates a repository. func (c *client) RepoPatch(in *model.Repo) (*model.Repo, error) { out := new(model.Repo) diff --git a/contrib/debian/drone/DEBIAN/conffiles b/contrib/debian/drone/DEBIAN/conffiles deleted file mode 100644 index 91e7ed3c9..000000000 --- a/contrib/debian/drone/DEBIAN/conffiles +++ /dev/null @@ -1,2 +0,0 @@ -/etc/init/drone.conf -/etc/drone/dronerc diff --git a/contrib/debian/drone/DEBIAN/control b/contrib/debian/drone/DEBIAN/control deleted file mode 100644 index 3f0a756a6..000000000 --- a/contrib/debian/drone/DEBIAN/control +++ /dev/null @@ -1,7 +0,0 @@ -Package: drone -Version: 0.4 -Section: base -Priority: optional -Architecture: amd64 -Maintainer: Brad Rydzewski -Description: Drone continuous integration server diff --git a/contrib/debian/drone/DEBIAN/postinst b/contrib/debian/drone/DEBIAN/postinst deleted file mode 100755 index fd835bb8a..000000000 --- a/contrib/debian/drone/DEBIAN/postinst +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh -set -e - -case "$1" in - abort-upgrade|abort-remove|abort-deconfigure|configure) - ;; - - *) - echo "postinst called with unknown argument \`$1'" >&2 - exit 1 - ;; -esac - -echo "Starting drone ..." -if [ -f /etc/init/drone.conf ]; then - if pidof /usr/local/bin/drone >/dev/null; then - service drone stop || exit $? - fi - service drone start && echo "Drone started." -fi - -#DEBHELPER# - -exit 0 diff --git a/contrib/debian/drone/DEBIAN/prerm b/contrib/debian/drone/DEBIAN/prerm deleted file mode 100755 index a0f7be8cb..000000000 --- a/contrib/debian/drone/DEBIAN/prerm +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/sh - -set -e -set -u - -case "$1" in - remove|remove-in-favour|deconfigure|deconfigure-in-favour) - if [ -f /etc/init/drone.conf ]; then - echo "Stopping drone ..." - service drone stop || exit $? - echo "Drone Stopped." - fi - ;; - - upgrade|failed-upgrade) - ;; - - *) - echo "prerm called with unknown argument \`$1'" >&2 - exit 1 - ;; -esac - -#DEBHELPER# - -exit 0 diff --git a/contrib/debian/drone/etc/drone/dronerc b/contrib/debian/drone/etc/drone/dronerc deleted file mode 100644 index b20198427..000000000 --- a/contrib/debian/drone/etc/drone/dronerc +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/bash - -# server configuration - -SERVER_ADDR=":80" -#SERVER_CERT="" -#SERVER_KEY="" - -# database configuration - -DATABASE_DRIVER="sqlite3" -DATABASE_CONFIG="/var/lib/drone/drone.sqlite" - -# remote configuration - -CLIENT="" # oauth2 client. REQUIRED -SECRET="" # oauth2 secret. REQUIRED - -REMOTE_DRIVER="github" -REMOTE_CONFIG="https://github.com?client_id=$CLIENT&client_secret=$SECRET" - -# docker configuration - -DOCKER_HOST="unix:///var/run/docker.sock" -#DOCKER_CERT="" -#DOCKER_KEY="" -#DOCKER_CA="" - -# plugin configuration - -PLUGIN_FILTER="plugins/*" diff --git a/contrib/debian/drone/etc/init/drone.conf b/contrib/debian/drone/etc/init/drone.conf deleted file mode 100644 index 7ca816794..000000000 --- a/contrib/debian/drone/etc/init/drone.conf +++ /dev/null @@ -1,12 +0,0 @@ -start on (filesystem and net-device-up) - -chdir /var/lib/drone -console log - -script - set -a - if [ -f /etc/drone/dronerc ]; then - . /etc/drone/dronerc - fi - /usr/local/bin/drone -end script diff --git a/contrib/docker/etc/nsswitch.conf b/contrib/docker/etc/nsswitch.conf deleted file mode 100644 index cb918b774..000000000 --- a/contrib/docker/etc/nsswitch.conf +++ /dev/null @@ -1,19 +0,0 @@ -# /etc/nsswitch.conf -# -# Example configuration of GNU Name Service Switch functionality. -# If you have the `glibc-doc-reference' and `info' packages installed, try: -# `info libc "Name Service Switch"' for information about this file. - -passwd: compat -group: compat -shadow: compat - -hosts: files dns -networks: files - -protocols: files -services: files -ethers: files -rpc: files - -netgroup: nis diff --git a/contrib/generate-amber.go b/contrib/generate-amber.go deleted file mode 100644 index 8e6175469..000000000 --- a/contrib/generate-amber.go +++ /dev/null @@ -1,6 +0,0 @@ -// +build ignore - -// This program converts amber templates to standard -// Go template files. - -package main diff --git a/contrib/generate-js.go b/contrib/generate-js.go deleted file mode 100644 index 7e77d3260..000000000 --- a/contrib/generate-js.go +++ /dev/null @@ -1,60 +0,0 @@ -// +build ignore - -// This program minifies JavaScript files -// $ go run generate-js.go -dir scripts/ -out scripts/drone.min.js - -package main - -import ( - "bytes" - "flag" - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" - - "github.com/dchest/jsmin" -) - -var ( - dir = flag.String("dir", "scripts/", "") - out = flag.String("o", "scripts/drone.min.js", "") -) - -func main() { - flag.Parse() - - var buf bytes.Buffer - - // walk the directory tree and write all - // javascript files to the buffer. - filepath.Walk(*dir, func(path string, info os.FileInfo, err error) error { - if filepath.Ext(path) != ".js" { - return nil - } - - f, err := os.Open(path) - if err != nil { - return nil - } - defer f.Close() - - // write the file name to the minified output - fmt.Fprintf(&buf, "// %s\n", path) - - // copy the file to the buffer - _, err = io.Copy(&buf, f) - return err - }) - - // minifies the javascript - data, err := jsmin.Minify(buf.Bytes()) - if err != nil { - fmt.Println(err) - os.Exit(1) - } - - // write the minified output - ioutil.WriteFile(*out, data, 0700) -} diff --git a/contrib/setup-sassc.sh b/contrib/setup-sassc.sh deleted file mode 100755 index 85659f90c..000000000 --- a/contrib/setup-sassc.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash -set -e - -cd /tmp - -# cleanup previously downloaded and unpacked files. -rm -rf libsass -rm -rf sassc - -# download the latest build of sassc -git clone --depth=1 git://github.com/sass/libsass.git -git clone --depth=1 git://github.com/sass/sassc.git - -export SASS_LIBSASS_PATH=/tmp/libsass - -# build the sassc binary -cd sassc -make - -# isntall the sassc binary -install -t /usr/local/bin bin/sassc diff --git a/contrib/setup-sqlite.sh b/contrib/setup-sqlite.sh deleted file mode 100755 index f68eba117..000000000 --- a/contrib/setup-sqlite.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash -set -e - -cd /tmp - -# cleanup previously downloaded and unpacked files. -rm -rf sqlite-autoconf-3081101.tar.gz -rm -rf sqlite-autoconf-3081101 - -# download sqlite -curl -O https://www.sqlite.org/2015/sqlite-autoconf-3081101.tar.gz -tar xzf sqlite-autoconf-3081101.tar.gz - -# build and install -cd sqlite-autoconf-3081101 -./configure -prefix=/scratch/usr/local -make -make install diff --git a/router/router.go b/router/router.go index a6f89f2bf..84410dfc8 100644 --- a/router/router.go +++ b/router/router.go @@ -2,7 +2,6 @@ package router import ( "net/http" - "strings" "github.com/gin-gonic/gin" @@ -10,17 +9,19 @@ import ( "github.com/drone/drone/router/middleware/session" "github.com/drone/drone/router/middleware/token" "github.com/drone/drone/server" - "github.com/drone/drone/static" - "github.com/drone/drone/template" + "github.com/drone/drone/server/template" + + "github.com/drone/drone-ui/dist" ) +// Load loads the router func Load(middleware ...gin.HandlerFunc) http.Handler { e := gin.New() e.Use(gin.Recovery()) e.SetHTMLTemplate(template.Load()) - e.StaticFS("/static", static.FileSystem()) + e.StaticFS("/static", dist.AssetFS()) e.Use(header.NoCache) e.Use(header.Options) @@ -29,35 +30,11 @@ func Load(middleware ...gin.HandlerFunc) http.Handler { e.Use(session.SetUser()) e.Use(token.Refresh) - e.GET("/", server.ShowIndex) - e.GET("/repos", server.ShowAllRepos) e.GET("/login", server.ShowLogin) e.GET("/login/form", server.ShowLoginForm) e.GET("/logout", server.GetLogout) + e.NoRoute(server.ShowIndex) - // TODO below will Go away with React UI - settings := e.Group("/settings") - { - settings.Use(session.MustUser()) - settings.GET("/profile", server.ShowUser) - } - repo := e.Group("/repos/:owner/:name") - { - repo.Use(session.SetRepo()) - repo.Use(session.SetPerm()) - repo.Use(session.MustPull) - - repo.GET("", server.ShowRepo) - repo.GET("/builds/:number", server.ShowBuild) - repo.GET("/builds/:number/:job", server.ShowBuild) - - repo_settings := repo.Group("/settings") - { - repo_settings.GET("", session.MustPush, server.ShowRepoConf) - repo_settings.GET("/encrypt", session.MustPush, server.ShowRepoEncrypt) - repo_settings.GET("/badges", server.ShowRepoBadges) - } - } // TODO above will Go away with React UI user := e.Group("/api/user") @@ -129,6 +106,16 @@ func Load(middleware ...gin.HandlerFunc) http.Handler { stream.GET("/:owner/:name", server.GetRepoEvents) stream.GET("/:owner/:name/:build/:number", server.GetStream) } + ws := e.Group("/ws") + { + ws.GET("/feed", server.EventStream) + ws.GET("/logs/:owner/:name/:build/:number", + session.SetRepo(), + session.SetPerm(), + session.MustPull, + server.LogStream, + ) + } auth := e.Group("/authorize") { @@ -184,34 +171,5 @@ func Load(middleware ...gin.HandlerFunc) http.Handler { // bots.POST("/slack/:command", Slack) // } - return normalize(e) -} - -// THIS HACK JOB IS GOING AWAY SOON. -// -// normalize is a helper function to work around the following -// issue with gin. https://github.com/gin-gonic/gin/issues/388 -func normalize(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - - parts := strings.Split(r.URL.Path, "/")[1:] - switch parts[0] { - case "settings", "bots", "repos", "api", "login", "logout", "", "authorize", "hook", "static", "gitlab": - // no-op - default: - - if len(parts) > 2 && parts[2] != "settings" { - parts = append(parts[:2], append([]string{"builds"}, parts[2:]...)...) - } - - // prefix the URL with /repo so that it - // can be effectively routed. - parts = append([]string{"", "repos"}, parts...) - - // reconstruct the path - r.URL.Path = strings.Join(parts, "/") - } - - h.ServeHTTP(w, r) - }) + return e } diff --git a/server/login.go b/server/login.go index af784222e..55dfbfce3 100644 --- a/server/login.go +++ b/server/login.go @@ -110,11 +110,7 @@ func GetLogin(c *gin.Context) { } httputil.SetCookie(c.Writer, c.Request, "user_sess", tokenstr) - redirect := httputil.GetCookie(c.Request, "user_last") - if len(redirect) == 0 { - redirect = "/" - } - c.Redirect(303, redirect) + c.Redirect(303, "/") } diff --git a/server/pages.go b/server/pages.go index 4d765face..74a73c206 100644 --- a/server/pages.go +++ b/server/pages.go @@ -1,192 +1,15 @@ package server import ( - "net/http" - "strconv" - "time" - "github.com/gin-gonic/gin" - "github.com/drone/drone/cache" - "github.com/drone/drone/model" "github.com/drone/drone/router/middleware/session" - "github.com/drone/drone/shared/httputil" "github.com/drone/drone/shared/token" - "github.com/drone/drone/store" ) +// ShowIndex serves the main Drone application page. func ShowIndex(c *gin.Context) { user := session.User(c) - if user == nil { - c.Redirect(http.StatusSeeOther, "/login") - return - } - - // get the repository list from the cache - repos, err := cache.GetRepos(c, user) - if err != nil { - c.String(400, err.Error()) - return - } - - // filter to only show the currently active ones - activeRepos, err := store.GetRepoListOf(c, repos) - if err != nil { - c.String(400, err.Error()) - return - } - - c.HTML(200, "index.html", gin.H{ - "User": user, - "Repos": activeRepos, - }) -} - -func ShowAllRepos(c *gin.Context) { - user := session.User(c) - if user == nil { - c.Redirect(http.StatusSeeOther, "/login") - return - } - - // get the repository list from the cache - repos, err := cache.GetRepos(c, user) - if err != nil { - c.String(400, err.Error()) - return - } - - c.HTML(200, "repos.html", gin.H{ - "User": user, - "Repos": repos, - }) -} - -func ShowLogin(c *gin.Context) { - c.HTML(200, "login.html", gin.H{"Error": c.Query("error")}) -} - -func ShowLoginForm(c *gin.Context) { - c.HTML(200, "login_form.html", gin.H{}) -} - -func ShowUser(c *gin.Context) { - user := session.User(c) - token, _ := token.New( - token.CsrfToken, - user.Login, - ).Sign(user.Hash) - - c.HTML(200, "user.html", gin.H{ - "User": user, - "Csrf": token, - }) -} - -func ShowRepo(c *gin.Context) { - user := session.User(c) - repo := session.Repo(c) - - builds, _ := store.GetBuildList(c, repo) - groups := []*model.BuildGroup{} - - var curr *model.BuildGroup - for _, build := range builds { - date := time.Unix(build.Created, 0).Format("Jan 2 2006") - if curr == nil || curr.Date != date { - curr = &model.BuildGroup{} - curr.Date = date - groups = append(groups, curr) - } - curr.Builds = append(curr.Builds, build) - } - - httputil.SetCookie(c.Writer, c.Request, "user_last", repo.FullName) - - c.HTML(200, "repo.html", gin.H{ - "User": user, - "Repo": repo, - "Builds": builds, - "Groups": groups, - }) - -} - -func ShowRepoConf(c *gin.Context) { - - user := session.User(c) - repo := session.Repo(c) - - token, _ := token.New( - token.CsrfToken, - user.Login, - ).Sign(user.Hash) - - c.HTML(200, "repo_config.html", gin.H{ - "User": user, - "Repo": repo, - "Csrf": token, - "Link": httputil.GetURL(c.Request), - }) -} - -func ShowRepoEncrypt(c *gin.Context) { - user := session.User(c) - repo := session.Repo(c) - - token, _ := token.New( - token.CsrfToken, - user.Login, - ).Sign(user.Hash) - - c.HTML(200, "repo_secret.html", gin.H{ - "User": user, - "Repo": repo, - "Csrf": token, - }) -} - -func ShowRepoBadges(c *gin.Context) { - user := session.User(c) - repo := session.Repo(c) - - c.HTML(200, "repo_badge.html", gin.H{ - "User": user, - "Repo": repo, - "Link": httputil.GetURL(c.Request), - }) -} - -func ShowBuild(c *gin.Context) { - user := session.User(c) - repo := session.Repo(c) - num, _ := strconv.Atoi(c.Param("number")) - seq, _ := strconv.Atoi(c.Param("job")) - if seq == 0 { - seq = 1 - } - - build, err := store.GetBuildNumber(c, repo, num) - if err != nil { - c.AbortWithError(404, err) - return - } - - jobs, err := store.GetJobList(c, build) - if err != nil { - c.AbortWithError(404, err) - return - } - - var job *model.Job - for _, j := range jobs { - if j.Number == seq { - job = j - break - } - } - - httputil.SetCookie(c.Writer, c.Request, "user_last", repo.FullName) var csrf string if user != nil { @@ -196,12 +19,20 @@ func ShowBuild(c *gin.Context) { ).Sign(user.Hash) } - c.HTML(200, "build.html", gin.H{ - "User": user, - "Repo": repo, - "Build": build, - "Jobs": jobs, - "Job": job, - "Csrf": csrf, + c.HTML(200, "index.html", gin.H{ + "user": user, + "csrf": csrf, }) } + +// ShowLogin is a legacy endpoint that now redirects to +// initiliaze the oauth flow +func ShowLogin(c *gin.Context) { + c.Redirect(303, "/authorize") +} + +// ShowLoginForm displays a login form for systems like Gogs that do not +// yet support oauth workflows. +func ShowLoginForm(c *gin.Context) { + c.HTML(200, "login.html", gin.H{}) +} diff --git a/server/stream.go b/server/stream.go index aae7bf2fd..b80bbc73a 100644 --- a/server/stream.go +++ b/server/stream.go @@ -5,17 +5,18 @@ import ( "encoding/json" "io" "strconv" - - "github.com/gin-gonic/gin" + "time" "github.com/drone/drone/bus" + "github.com/drone/drone/cache" "github.com/drone/drone/model" "github.com/drone/drone/router/middleware/session" "github.com/drone/drone/store" "github.com/drone/drone/stream" - log "github.com/Sirupsen/logrus" - + "github.com/Sirupsen/logrus" + "github.com/gin-gonic/gin" + "github.com/gorilla/websocket" "github.com/manucorporat/sse" ) @@ -30,14 +31,14 @@ func GetRepoEvents(c *gin.Context) { defer func() { bus.Unsubscribe(c, eventc) close(eventc) - log.Infof("closed event stream") + logrus.Infof("closed event stream") }() c.Stream(func(w io.Writer) bool { select { case event := <-eventc: if event == nil { - log.Infof("nil event received") + logrus.Infof("nil event received") return false } @@ -75,13 +76,13 @@ func GetStream(c *gin.Context) { build, err := store.GetBuildNumber(c, repo, buildn) if err != nil { - log.Debugln("stream cannot get build number.", err) + logrus.Debugln("stream cannot get build number.", err) c.AbortWithError(404, err) return } job, err := store.GetJobNumber(c, build, jobn) if err != nil { - log.Debugln("stream cannot get job number.", err) + logrus.Debugln("stream cannot get job number.", err) c.AbortWithError(404, err) return } @@ -112,5 +113,174 @@ func GetStream(c *gin.Context) { c.Writer.Flush() } - log.Debugf("Closed stream %s#%d", repo.FullName, build.Number) + logrus.Debugf("Closed stream %s#%d", repo.FullName, build.Number) +} + +var ( + // Time allowed to write the file to the client. + writeWait = 5 * time.Second + + // Time allowed to read the next pong message from the client. + pongWait = 60 * time.Second + + // Send pings to client with this period. Must be less than pongWait. + pingPeriod = 30 * time.Second +) + +// LogStream streams the build log output to the client. +func LogStream(c *gin.Context) { + repo := session.Repo(c) + buildn, _ := strconv.Atoi(c.Param("build")) + jobn, _ := strconv.Atoi(c.Param("number")) + + c.Writer.Header().Set("Content-Type", "text/event-stream") + + build, err := store.GetBuildNumber(c, repo, buildn) + if err != nil { + logrus.Debugln("stream cannot get build number.", err) + c.AbortWithError(404, err) + return + } + job, err := store.GetJobNumber(c, build, jobn) + if err != nil { + logrus.Debugln("stream cannot get job number.", err) + c.AbortWithError(404, err) + return + } + if job.Status != model.StatusRunning { + logrus.Debugln("stream not found.") + c.AbortWithStatus(404) + return + } + + ws, err := upgrader.Upgrade(c.Writer, c.Request, nil) + if err != nil { + if _, ok := err.(websocket.HandshakeError); !ok { + logrus.Errorf("Cannot upgrade websocket. %s", err) + } + return + } + logrus.Debugf("Successfull upgraded websocket") + + ticker := time.NewTicker(pingPeriod) + defer ticker.Stop() + + rc, err := stream.Reader(c, stream.ToKey(job.ID)) + if err != nil { + c.AbortWithError(404, err) + return + } + + quitc := make(chan bool) + defer func() { + quitc <- true + close(quitc) + rc.Close() + ws.Close() + logrus.Debug("Successfully closed websocket") + }() + + go func() { + defer func() { + recover() + }() + for { + select { + case <-quitc: + return + case <-ticker.C: + err := ws.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(writeWait)) + if err != nil { + return + } + } + } + }() + + var scanner = bufio.NewScanner(rc) + var b []byte + for scanner.Scan() { + b = scanner.Bytes() + if len(b) == 0 { + continue + } + ws.SetWriteDeadline(time.Now().Add(writeWait)) + ws.WriteMessage(websocket.TextMessage, b) + } +} + +// EventStream produces the User event stream, sending all repository, build +// and agent events to the client. +func EventStream(c *gin.Context) { + ws, err := upgrader.Upgrade(c.Writer, c.Request, nil) + if err != nil { + if _, ok := err.(websocket.HandshakeError); !ok { + logrus.Errorf("Cannot upgrade websocket. %s", err) + } + return + } + logrus.Debugf("Successfull upgraded websocket") + + user := session.User(c) + repo := map[string]bool{} + if user != nil { + repo, _ = cache.GetRepoMap(c, user) + } + + ticker := time.NewTicker(pingPeriod) + quitc := make(chan bool) + eventc := make(chan *bus.Event, 10) + bus.Subscribe(c, eventc) + defer func() { + ticker.Stop() + bus.Unsubscribe(c, eventc) + quitc <- true + close(quitc) + close(eventc) + ws.Close() + logrus.Debug("Successfully closed websocket") + }() + + go func() { + defer func() { + recover() + }() + for { + select { + case <-quitc: + return + case event := <-eventc: + if event == nil { + return + } + if repo[event.Repo.FullName] || !event.Repo.IsPrivate { + ws.SetWriteDeadline(time.Now().Add(writeWait)) + ws.WriteJSON(event) + } + case <-ticker.C: + err := ws.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(writeWait)) + if err != nil { + return + } + } + } + }() + + reader(ws) +} + +func reader(ws *websocket.Conn) { + defer ws.Close() + ws.SetReadLimit(512) + ws.SetReadDeadline(time.Now().Add(pongWait)) + ws.SetPongHandler(func(string) error { + ws.SetReadDeadline(time.Now().Add(pongWait)) + return nil + }) + for { + _, _, err := ws.ReadMessage() + if err != nil { + break + } + } } diff --git a/server/template/files/index.html b/server/template/files/index.html new file mode 100644 index 000000000..248343c87 --- /dev/null +++ b/server/template/files/index.html @@ -0,0 +1,22 @@ + + + + + + + {{ if .csrf }}{{ end }} + + + + + + + +
+ + + + + diff --git a/server/template/files/login.html b/server/template/files/login.html new file mode 100644 index 000000000..134a67aaa --- /dev/null +++ b/server/template/files/login.html @@ -0,0 +1,20 @@ + + + + + + + + + + + + +
+ + + +
+ + + diff --git a/server/template/files/logout.html b/server/template/files/logout.html new file mode 100644 index 000000000..209daa7b0 --- /dev/null +++ b/server/template/files/logout.html @@ -0,0 +1 @@ +LOGOUT diff --git a/server/template/template.go b/server/template/template.go new file mode 100644 index 000000000..014605d95 --- /dev/null +++ b/server/template/template.go @@ -0,0 +1,31 @@ +package template + +//go:generate go-bindata -pkg template -o template_gen.go files/ + +import ( + "encoding/json" + "html/template" + "path/filepath" +) + +// Load loads the templates from the embedded file map. This function will not +// compile if go generate is not executed before. +func Load() *template.Template { + dir, _ := AssetDir("files") + tmpl := template.New("_").Funcs(template.FuncMap{"json": marshal}) + for _, name := range dir { + path := filepath.Join("files", name) + src := MustAsset(path) + tmpl = template.Must( + tmpl.New(name).Parse(string(src)), + ) + } + + return tmpl +} + +// marshal is a helper function to render data as JSON inside the template. +func marshal(v interface{}) template.JS { + a, _ := json.Marshal(v) + return template.JS(a) +} diff --git a/static/images/docker.svg b/static/images/docker.svg deleted file mode 100644 index ae937ca1c..000000000 --- a/static/images/docker.svg +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/static/images/docker_blue.svg b/static/images/docker_blue.svg deleted file mode 100644 index bb007f66e..000000000 --- a/static/images/docker_blue.svg +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/static/images/docker_white.svg b/static/images/docker_white.svg deleted file mode 100644 index 519cd2081..000000000 --- a/static/images/docker_white.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/static/images/dummy.png b/static/images/dummy.png deleted file mode 100644 index 2b428fa16..000000000 Binary files a/static/images/dummy.png and /dev/null differ diff --git a/static/images/favicon.ico b/static/images/favicon.ico deleted file mode 100644 index a7607313a..000000000 Binary files a/static/images/favicon.ico and /dev/null differ diff --git a/static/images/favicon.png b/static/images/favicon.png deleted file mode 100644 index 5ff57c620..000000000 Binary files a/static/images/favicon.png and /dev/null differ diff --git a/static/images/logo_dark.svg b/static/images/logo_dark.svg deleted file mode 100644 index be92f1150..000000000 --- a/static/images/logo_dark.svg +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - diff --git a/static/images/logo_light.svg b/static/images/logo_light.svg deleted file mode 100644 index af16f2873..000000000 --- a/static/images/logo_light.svg +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - diff --git a/static/images/ubuntu.svg b/static/images/ubuntu.svg deleted file mode 100644 index 850665f7a..000000000 --- a/static/images/ubuntu.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/static/scripts/build.js b/static/scripts/build.js deleted file mode 100644 index a152d09bf..000000000 --- a/static/scripts/build.js +++ /dev/null @@ -1,200 +0,0 @@ - - -function JobViewModel(repo, build, job, status) { - var self = this; - self.status = status; - - self.stream = function() { - $("#output").html(""); - $("#restart").hide(); - $("#cancel").show(); - - var buf = new Drone.Buffer(); - buf.start(document.getElementById("output")); - - $( "#tail" ).show(); - $( "#tail" ).click(function() { - buf.autoFollow = !buf.autoFollow; - if (buf.autoFollow) { - $( "#tail i" ).text("pause"); - $( "#tail" ).show(); - - // scroll to the bottom of the page - window.scrollTo(0, document.body.scrollHeight); - } else { - $( "#tail i" ).text("expand_more"); - $( "#tail" ).show(); - } - }) - - Stream(repo, build, job, function(out){ - buf.write(out); - }); - }; - - if (status !== "running" && status !== "pending") { - Logs(repo, build, job); - $("#restart").show(); - } - - if (status === "running") { - self.stream(); - } - - $("#restart").click(function() { - $("#restart").hide(); - $("#output").html(""); - $(".status").attr("class", "status pending").text("pending"); - - $.ajax({ - url: "/api/repos/"+repo+"/builds/"+build, - type: "POST", - success: function( data ) { }, - error: function( data ) { - console.log(data); - } - }); - }) - - $("#cancel").click(function() { - $("#cancel").hide(); - - $.ajax({ - url: "/api/repos/"+repo+"/builds/"+build+"/"+job, - type: "DELETE", - success: function( data ) { }, - error: function( data ) { - console.log(data); - } - }); - }) - - - Subscribe(repo, function(data){ - if (!data.jobs) { - return; - } - if (data.number !== build) { - return; - } - - var before = self.status; - self.status = data.jobs[job-1].status; - - // update the status for each job in the view - for (var i=0;i").attr("data-title", line.proc); - $("#output").append(pre); - - // create the buffer for the group of output - var buf = new Drone.Buffer(); - buf.start(pre[0]); - - // add items to the group - group = { - pre: pre, - buf: buf, - }; - groups[line.proc]=group; - } - - group.buf.write(line.out+"\n"); - } - - for (var i=0; i b.full_name().toLowerCase() ? 1 : -1; -} - -/** - * Creates an observable object that stores a list of hook event - * types (push, pull request, etc) and true or false if enabled. - */ -function Hook(repo) { - var data = { - "pull_request" : repo.events.indexOf("pull_request") !== -1, - "push" : repo.events.indexOf("push") !== -1, - "tag" : repo.events.indexOf("tag") !== -1, - "deploy" : repo.events.indexOf("deploy") !== -1 - }; - - this.pull_request = ko.observable(data.pull_request); - this.push = ko.observable(data.push); - this.tag = ko.observable(data.tag); - this.deploy = ko.observable(data.deploy); -} - -/** - * Creates an observable user. - */ -function User(data) { - this.login = ko.observable(data.login); - this.email = ko.observable(data.email); - this.avatar_url = ko.observable(data.avatar_url); - this.active = ko.observable(data.active); - this.admin = ko.observable(data.admin); -} - -/** - * Compares two user objects by login. Used to sort - * a list of users. - */ -function UserCompare(a, b) { - return a.login().toLowerCase() > b.login().toLowerCase() ? 1 : -1; -} diff --git a/static/scripts/nodes.js b/static/scripts/nodes.js deleted file mode 100644 index 74b7ec2c4..000000000 --- a/static/scripts/nodes.js +++ /dev/null @@ -1,74 +0,0 @@ - -function NodeViewModel() { - var self = this; - - // handle requests to create a new node. - $(".modal-node button").click(function(e) { - - var node = { - address : $("#addr").val(), - key : $("#key").val(), - cert : $("#cert").val(), - ca : $("#ca").val() - }; - - $.ajax({ - url: "/api/nodes", - type: "POST", - contentType: "application/json", - data: JSON.stringify(node), - success: function( data ) { - // clears the form value - $(".modal-node input").val(""); - - var el = $("
").attr("class", "col-sm-4").append( - $("
").attr("class", "card").attr("data-id", data.id).append( - $("
").attr("class", "card-header").append( - $("").attr("class", "linux_amd64") - ) - ).append( - $("
").attr("class", "card-block").append( - $("

").text(data.address) - ).append( - $("

").attr("class", "card-text").text(data.architecture) - ).append( - $("

").attr("class", "btn-group").append( - $("