diff --git a/.travis.yml b/.travis.yml index c6b9cdfc..ae717ce6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ services: env: global: - - VERSION=0.29.7 + - VERSION=0.29.8 - DB_HOST=localhost - DB_USER=travis - DB_PASS= @@ -65,8 +65,9 @@ before_script: - go get script: - - if [[ "$TRAVIS_BRANCH" == "master" ]]; then /bin/bash -c .travis/compile.sh; fi - - go test -v -covermode=count -coverprofile=coverage.out && $GOPATH/bin/goveralls -coverprofile=coverage.out -service=travis -repotoken $COVERALLS + - /bin/bash -c .travis/compile.sh + - go test -v -covermode=count -coverprofile=coverage.out + - if [[ "$TRAVIS_BRANCH" == "master" ]]; then $GOPATH/bin/goveralls -coverprofile=coverage.out -service=travis -repotoken $COVERALLS; fi after_success: - if [[ "$TRAVIS_BRANCH" == "master" ]]; then travis_wait 30 docker pull karalabe/xgo-latest; fi diff --git a/Dockerfile b/Dockerfile index 17a5dc5e..316d0aca 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM alpine:latest -ENV VERSION=v0.29.7 +ENV VERSION=v0.29.8 RUN apk --no-cache add libstdc++ ca-certificates RUN wget -q https://github.com/hunterlong/statup/releases/download/$VERSION/statup-linux-alpine.tar.gz && \ diff --git a/core/users.go b/core/users.go index 9ab74707..a341aa9b 100644 --- a/core/users.go +++ b/core/users.go @@ -32,6 +32,13 @@ func (u *User) Delete() error { return user.Delete() } +func (u *User) Update() error { + u.CreatedAt = time.Now() + col := DbSession.Collection("users") + user := col.Find("id", u.Id) + return user.Update(u) +} + func (u *User) Create() (int64, error) { u.CreatedAt = time.Now() u.Password = utils.HashPassword(u.Password) diff --git a/handlers/routes.go b/handlers/routes.go index 145e55ca..42476c54 100644 --- a/handlers/routes.go +++ b/handlers/routes.go @@ -29,9 +29,10 @@ func Router() *mux.Router { r.Handle("/service/{id}/delete_failures", http.HandlerFunc(ServicesDeleteFailuresHandler)).Methods("GET") r.Handle("/service/{id}/checkin", http.HandlerFunc(CheckinCreateUpdateHandler)).Methods("POST") r.Handle("/users", http.HandlerFunc(UsersHandler)).Methods("GET") - r.Handle("/user/{id}", http.HandlerFunc(UsersHandler)).Methods("GET") r.Handle("/users", http.HandlerFunc(CreateUserHandler)).Methods("POST") - r.Handle("/users/{id}/delete", http.HandlerFunc(UsersDeleteHandler)).Methods("GET") + r.Handle("/user/{id}", http.HandlerFunc(UsersEditHandler)).Methods("GET") + r.Handle("/user/{id}", http.HandlerFunc(UpdateUserHandler)).Methods("POST") + r.Handle("/user/{id}/delete", http.HandlerFunc(UsersDeleteHandler)).Methods("GET") r.Handle("/settings", http.HandlerFunc(PluginsHandler)).Methods("GET") r.Handle("/settings", http.HandlerFunc(SaveSettingsHandler)).Methods("POST") r.Handle("/settings/css", http.HandlerFunc(SaveSASSHandler)).Methods("POST") diff --git a/handlers/users.go b/handlers/users.go index 39d8d768..854d1bdf 100644 --- a/handlers/users.go +++ b/handlers/users.go @@ -1,6 +1,7 @@ package handlers import ( + "fmt" "github.com/gorilla/mux" "github.com/hunterlong/statup/core" "github.com/hunterlong/statup/types" @@ -18,13 +19,16 @@ func SessionUser(r *http.Request) *types.User { var user *types.User col := core.DbSession.Collection("users") res := col.Find("id", uuid) - res.One(&user) + err := res.One(&user) + if err != nil { + utils.Log(3, fmt.Sprintf("cannot fetch user %v", uuid)) + return nil + } return user } func UsersHandler(w http.ResponseWriter, r *http.Request) { - auth := IsAuthenticated(r) - if !auth { + if !IsAuthenticated(r) { http.Redirect(w, r, "/", http.StatusSeeOther) return } @@ -32,9 +36,41 @@ func UsersHandler(w http.ResponseWriter, r *http.Request) { ExecuteResponse(w, r, "users.html", users) } +func UsersEditHandler(w http.ResponseWriter, r *http.Request) { + if !IsAuthenticated(r) { + http.Redirect(w, r, "/", http.StatusSeeOther) + return + } + vars := mux.Vars(r) + id, _ := strconv.Atoi(vars["id"]) + user, _ := core.SelectUser(int64(id)) + ExecuteResponse(w, r, "user.html", user) +} + +func UpdateUserHandler(w http.ResponseWriter, r *http.Request) { + if !IsAuthenticated(r) { + http.Redirect(w, r, "/", http.StatusSeeOther) + return + } + r.ParseForm() + vars := mux.Vars(r) + id, _ := strconv.Atoi(vars["id"]) + user, _ := core.SelectUser(int64(id)) + + user.Username = r.PostForm.Get("username") + user.Email = r.PostForm.Get("email") + user.Admin = (r.PostForm.Get("admin") == "on") + password := r.PostForm.Get("password") + if password != "##########" { + user.Password = utils.HashPassword(password) + } + user.Update() + users, _ := core.SelectAllUsers() + ExecuteResponse(w, r, "users.html", users) +} + func CreateUserHandler(w http.ResponseWriter, r *http.Request) { - auth := IsAuthenticated(r) - if !auth { + if !IsAuthenticated(r) { http.Redirect(w, r, "/", http.StatusSeeOther) return } @@ -59,8 +95,7 @@ func CreateUserHandler(w http.ResponseWriter, r *http.Request) { } func UsersDeleteHandler(w http.ResponseWriter, r *http.Request) { - auth := IsAuthenticated(r) - if !auth { + if !IsAuthenticated(r) { http.Redirect(w, r, "/", http.StatusSeeOther) return } diff --git a/main_test.go b/main_test.go index cc0739b1..f93064e6 100644 --- a/main_test.go +++ b/main_test.go @@ -64,6 +64,9 @@ func TestRunAll(t *testing.T) { t.Run(dbt+" Create Users", func(t *testing.T) { RunUser_Create(t) }) + t.Run(dbt+" Update User", func(t *testing.T) { + RunUser_Update(t) + }) t.Run(dbt+" Create Non Unique Users", func(t *testing.T) { t.SkipNow() RunUser_NonUniqueCreate(t) @@ -134,6 +137,9 @@ func TestRunAll(t *testing.T) { t.Run(dbt+" HTTP /users", func(t *testing.T) { RunUsersHandler(t) }) + t.Run(dbt+" HTTP /user/1", func(t *testing.T) { + RunUserViewHandler(t) + }) t.Run(dbt+" HTTP /services", func(t *testing.T) { RunServicesHandler(t) }) @@ -264,6 +270,7 @@ func RunUser_Create(t *testing.T) { Username: "admin", Password: "admin", Email: "info@testuser.com", + Admin: true, } id, err := user.Create() assert.Nil(t, err) @@ -279,6 +286,17 @@ func RunUser_Create(t *testing.T) { assert.Equal(t, int64(2), id) } +func RunUser_Update(t *testing.T) { + user, err := core.SelectUser(1) + user.Email = "info@updatedemail.com" + assert.Nil(t, err) + err = user.Update() + assert.Nil(t, err) + updatedUser, err := core.SelectUser(1) + assert.Nil(t, err) + assert.Equal(t, "info@updatedemail.com", updatedUser.Email) +} + func RunUser_NonUniqueCreate(t *testing.T) { user := &core.User{ Username: "admin", @@ -481,6 +499,15 @@ func RunUsersHandler(t *testing.T) { assert.True(t, strings.Contains(rr.Body.String(), "footer")) } +func RunUserViewHandler(t *testing.T) { + req, err := http.NewRequest("GET", "/user/1", nil) + assert.Nil(t, err) + rr := httptest.NewRecorder() + route.ServeHTTP(rr, req) + assert.True(t, strings.Contains(rr.Body.String(), "<title>Statup | Users</title>")) + assert.True(t, strings.Contains(rr.Body.String(), "footer")) +} + func RunServicesHandler(t *testing.T) { req, err := http.NewRequest("GET", "/services", nil) assert.Nil(t, err) diff --git a/source/js/main.js b/source/js/main.js index 84ccf638..e87e2e29 100644 --- a/source/js/main.js +++ b/source/js/main.js @@ -11,6 +11,15 @@ $('form').submit(function() { }); +$(".confirm-btn").on('click', function() { + var r = confirm("Are you sure you want to delete?"); + if (r == true) { + return true; + } else { + return false; + } +}); + var ranVar = false; var ranTheme = false; diff --git a/source/tmpl/user.html b/source/tmpl/user.html index 06cce143..5421458d 100644 --- a/source/tmpl/user.html +++ b/source/tmpl/user.html @@ -22,64 +22,42 @@ <div class="col-12"> - <h3>Users</h3> - - <table class="table table-striped"> - <thead> - <tr> - <th scope="col">Username</th> - <th scope="col"></th> - </tr> - </thead> - <tbody> - {{range .}} - <tr> - <td>{{.Username}}</td> - <td class="text-right"> - <div class="btn-group"> - <a href="/users/{{.Id}}/delete" class="btn btn-danger">Delete</a> - </div> - </td> - </tr> - {{end}} - </tbody> - </table> - - <h3>Create User</h3> - <form action="/users" method="POST"> + <h3>User {{.Username}}</h3> + <form action="/user/{{.Id}}" method="POST"> <div class="form-group row"> <label for="username" class="col-sm-4 col-form-label">Username</label> <div class="col-sm-4"> - <input type="text" name="username" class="form-control" id="username" placeholder="Username" required> + <input type="text" name="username" class="form-control" value="{{.Username}}" id="username" placeholder="Username" required> </div> <div class="col-sm-4"> - <span class="switch"> - <input type="checkbox" name="admin" class="switch" id="switch-normal"> - <label for="switch-normal">Administrator</label> - </span> + <span class="switch"> + <input type="checkbox" name="admin" class="switch" id="switch-normal"{{if .Admin}} checked{{end}}> + <label for="switch-normal">Administrator</label> + </span> </div> </div> <div class="form-group row"> <label for="email" class="col-sm-4 col-form-label">Email Address</label> <div class="col-sm-8"> - <input type="email" name="email" class="form-control" id="email" placeholder="user@domain.com" required> + <input type="email" name="email" class="form-control" id="email" value="{{.Email}}" placeholder="user@domain.com" required> </div> </div> <div class="form-group row"> <label for="password" class="col-sm-4 col-form-label">Password</label> <div class="col-sm-8"> - <input type="password" name="password" class="form-control" id="password" placeholder="Password" required> + <input type="password" name="password" class="form-control" id="password" value="##########" placeholder="Password" required> </div> </div> <div class="form-group row"> <label for="password_confirm" class="col-sm-4 col-form-label">Confirm Password</label> <div class="col-sm-8"> - <input type="password" name="password_confirm" class="form-control" id="password_confirm" placeholder="Confirm Password" required> + <input type="password" name="password_confirm" class="form-control" id="password_confirm" value="##########" placeholder="Confirm Password" required> </div> </div> <div class="form-group row"> <div class="col-sm-12"> - <button type="submit" class="btn btn-primary btn-block">Create User</button> + <input type="hidden" name="user_id" value="{{.Id}}"> + <button type="submit" class="btn btn-primary btn-block">Update User</button> </div> </div> </form> diff --git a/source/tmpl/users.html b/source/tmpl/users.html index 364e8237..1f759c04 100644 --- a/source/tmpl/users.html +++ b/source/tmpl/users.html @@ -35,9 +35,10 @@ {{range .}} <tr> <td>{{.Username}}</td> - <td class="text-right"> + <td class="text-right" id="user_{{.Id}}"> <div class="btn-group"> - <a href="/users/{{.Id}}/delete" class="btn btn-danger">Delete</a> + <a href="/user/{{.Id}}" class="btn btn-primary">Edit</a> + <a href="/user/{{.Id}}/delete" class="btn btn-danger confirm-btn" data-id="user_{{.Id}}">Delete</a> </div> </td> </tr>