diff --git a/Makefile b/Makefile
index 5fd1f114..3589077b 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-VERSION=0.79.6
+VERSION=0.79.7
BINARY_NAME=statup
GOPATH:=$(GOPATH)
GOCMD=go
diff --git a/cmd/main_test.go b/cmd/main_test.go
index 07746929..c14952b3 100644
--- a/cmd/main_test.go
+++ b/cmd/main_test.go
@@ -110,7 +110,6 @@ func TestRunAll(t *testing.T) {
})
t.Run(dbt+" Select Core", func(t *testing.T) {
RunSelectCoreMYQL(t, dbt)
- t.Log(core.CoreApp)
})
t.Run(dbt+" Select Services", func(t *testing.T) {
RunSelectAllMysqlServices(t)
@@ -556,6 +555,7 @@ func RunUsersHandler(t *testing.T) {
assert.Nil(t, err)
rr := httptest.NewRecorder()
route.ServeHTTP(rr, req)
+ t.Log(rr.Body.String())
assert.True(t, strings.Contains(rr.Body.String(), "
Statup | Users"))
assert.True(t, strings.Contains(rr.Body.String(), "footer"))
assert.True(t, handlers.IsAuthenticated(req))
@@ -566,6 +566,7 @@ func RunUserViewHandler(t *testing.T) {
assert.Nil(t, err)
rr := httptest.NewRecorder()
route.ServeHTTP(rr, req)
+ t.Log(rr.Body.String())
assert.True(t, strings.Contains(rr.Body.String(), "Statup | testadmin"))
assert.True(t, strings.Contains(rr.Body.String(), "footer"))
assert.True(t, handlers.IsAuthenticated(req))
diff --git a/core/users.go b/core/users.go
index f87d9eaa..a22965b0 100644
--- a/core/users.go
+++ b/core/users.go
@@ -32,6 +32,13 @@ func ReturnUser(u *types.User) *user {
return &user{u}
}
+// CountUsers returns the amount of users
+func CountUsers() int64 {
+ var amount int64
+ usersDB().Count(&amount)
+ return amount
+}
+
// SelectUser returns the user based on the user's ID.
func SelectUser(id int64) (*user, error) {
var user user
diff --git a/handlers/api.go b/handlers/api.go
index c3808af7..2b529cf1 100644
--- a/handlers/api.go
+++ b/handlers/api.go
@@ -17,6 +17,7 @@ package handlers
import (
"encoding/json"
+ "errors"
"fmt"
"github.com/gorilla/mux"
"github.com/hunterlong/statup/core"
@@ -30,9 +31,10 @@ import (
type apiResponse struct {
Status string `json:"status"`
- Object string `json:"type"`
- Id int64 `json:"id"`
- Method string `json:"method"`
+ Object string `json:"type,omitempty"`
+ Id int64 `json:"id,omitempty"`
+ Method string `json:"method,omitempty"`
+ Error string `json:"error,omitempty"`
}
func apiIndexHandler(w http.ResponseWriter, r *http.Request) {
@@ -63,7 +65,8 @@ func apiRenewHandler(w http.ResponseWriter, r *http.Request) {
core.CoreApp.ApiSecret = utils.NewSHA1Hash(40)
core.CoreApp, err = core.UpdateCore(core.CoreApp)
if err != nil {
- utils.Log(3, err)
+ sendErrorJson(err, w, r)
+ return
}
http.Redirect(w, r, "/settings", http.StatusSeeOther)
}
@@ -84,7 +87,7 @@ func apiServiceDataHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
service := core.SelectService(utils.StringInt(vars["id"]))
if service == nil {
- http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
+ sendErrorJson(errors.New("service data not found"), w, r)
return
}
fields := parseGet(r)
@@ -106,7 +109,7 @@ func apiServicePingDataHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
service := core.SelectService(utils.StringInt(vars["id"]))
if service == nil {
- http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
+ sendErrorJson(errors.New("service not found"), w, r)
return
}
fields := parseGet(r)
@@ -127,7 +130,7 @@ func apiServiceHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
service := core.SelectServicer(utils.StringInt(vars["id"]))
if service == nil {
- http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
+ sendErrorJson(errors.New("service not found"), w, r)
return
}
@@ -144,13 +147,13 @@ func apiCreateServiceHandler(w http.ResponseWriter, r *http.Request) {
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&service)
if err != nil {
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+ sendErrorJson(err, w, r)
return
}
newService := core.ReturnService(service)
_, err = newService.Create(true)
if err != nil {
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+ sendErrorJson(err, w, r)
return
}
w.Header().Set("Content-Type", "application/json")
@@ -165,7 +168,7 @@ func apiServiceUpdateHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
service := core.SelectService(utils.StringInt(vars["id"]))
if service == nil {
- http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
+ sendErrorJson(errors.New("service not found"), w, r)
return
}
var updatedService *types.Service
@@ -175,7 +178,7 @@ func apiServiceUpdateHandler(w http.ResponseWriter, r *http.Request) {
service = core.ReturnService(updatedService)
err := service.Update(true)
if err != nil {
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+ sendErrorJson(err, w, r)
return
}
service.Check(true)
@@ -191,12 +194,12 @@ func apiServiceDeleteHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
service := core.SelectService(utils.StringInt(vars["id"]))
if service == nil {
- http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
+ sendErrorJson(errors.New("service not found"), w, r)
return
}
err := service.Delete()
if err != nil {
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+ sendErrorJson(err, w, r)
return
}
output := apiResponse{
@@ -223,113 +226,6 @@ func apiAllServicesHandler(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(servicesOut)
}
-func apiUserHandler(w http.ResponseWriter, r *http.Request) {
- if !isAPIAuthorized(r) {
- sendUnauthorizedJson(w, r)
- return
- }
- vars := mux.Vars(r)
- user, err := core.SelectUser(utils.StringInt(vars["id"]))
- if err != nil {
- http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
- return
- }
- w.Header().Set("Content-Type", "application/json")
- json.NewEncoder(w).Encode(user)
-}
-
-func apiUserUpdateHandler(w http.ResponseWriter, r *http.Request) {
- if !isAPIAuthorized(r) {
- sendUnauthorizedJson(w, r)
- return
- }
- vars := mux.Vars(r)
- user, err := core.SelectUser(utils.StringInt(vars["id"]))
- if err != nil {
- http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
- return
- }
- var updateUser *types.User
- decoder := json.NewDecoder(r.Body)
- decoder.Decode(&updateUser)
- updateUser.Id = user.Id
- user = core.ReturnUser(updateUser)
- err = user.Update()
- if err != nil {
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
- return
- }
- w.Header().Set("Content-Type", "application/json")
- json.NewEncoder(w).Encode(user)
-}
-
-func apiUserDeleteHandler(w http.ResponseWriter, r *http.Request) {
- if !isAPIAuthorized(r) {
- sendUnauthorizedJson(w, r)
- return
- }
- vars := mux.Vars(r)
- user, err := core.SelectUser(utils.StringInt(vars["id"]))
- if err != nil {
- http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
- return
- }
- err = user.Delete()
- if err != nil {
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
- return
- }
- output := apiResponse{
- Object: "user",
- Method: "delete",
- Id: user.Id,
- Status: "success",
- }
- w.Header().Set("Content-Type", "application/json")
- json.NewEncoder(w).Encode(output)
-}
-
-func apiAllUsersHandler(w http.ResponseWriter, r *http.Request) {
- if !isAPIAuthorized(r) {
- sendUnauthorizedJson(w, r)
- return
- }
- users, err := core.SelectAllUsers()
- if err != nil {
- utils.Log(3, err)
- }
- w.Header().Set("Content-Type", "application/json")
- json.NewEncoder(w).Encode(users)
-}
-
-func apiCreateUsersHandler(w http.ResponseWriter, r *http.Request) {
- if !isAPIAuthorized(r) {
- sendUnauthorizedJson(w, r)
- return
- }
- var user *types.User
- decoder := json.NewDecoder(r.Body)
- err := decoder.Decode(&user)
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
- newUser := core.ReturnUser(user)
- uId, err := newUser.Create()
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
- output := apiResponse{
- Object: "user",
- Method: "create",
- Id: uId,
- Status: "success",
- }
- w.Header().Set("Content-Type", "application/json")
- json.NewEncoder(w).Encode(output)
-}
-
func apiNotifierGetHandler(w http.ResponseWriter, r *http.Request) {
if !isAPIAuthorized(r) {
sendUnauthorizedJson(w, r)
@@ -338,7 +234,7 @@ func apiNotifierGetHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
_, notifierObj, err := notifier.SelectNotifier(vars["notifier"])
if err != nil {
- http.Error(w, fmt.Sprintf("%v notifier was not found", vars["notifier"]), http.StatusInternalServerError)
+ sendErrorJson(err, w, r)
return
}
w.Header().Set("Content-Type", "application/json")
@@ -358,7 +254,7 @@ func apiNotifierUpdateHandler(w http.ResponseWriter, r *http.Request) {
notifer, not, err := notifier.SelectNotifier(vars["notifier"])
if err != nil {
- http.Error(w, fmt.Sprintf("%v notifier was not found", vars["notifier"]), http.StatusInternalServerError)
+ sendErrorJson(err, w, r)
return
}
@@ -374,7 +270,8 @@ func apiNotifierUpdateHandler(w http.ResponseWriter, r *http.Request) {
_, err = notifier.Update(not, notifer)
if err != nil {
- utils.Log(3, fmt.Sprintf("issue updating notifier: %v", err))
+ sendErrorJson(err, w, r)
+ return
}
notifier.OnSave(notifer.Method)
@@ -389,7 +286,7 @@ func apiAllMessagesHandler(w http.ResponseWriter, r *http.Request) {
}
messages, err := core.SelectMessages()
if err != nil {
- http.Error(w, fmt.Sprintf("error fetching all messages: %v", err), http.StatusInternalServerError)
+ sendErrorJson(err, w, r)
return
}
w.Header().Set("Content-Type", "application/json")
@@ -404,7 +301,7 @@ func apiMessageGetHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
message, err := core.SelectMessage(utils.StringInt(vars["id"]))
if err != nil {
- http.Error(w, fmt.Sprintf("message #%v was not found", vars["id"]), http.StatusInternalServerError)
+ sendErrorJson(err, w, r)
return
}
w.Header().Set("Content-Type", "application/json")
@@ -419,12 +316,12 @@ func apiMessageDeleteHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
message, err := core.SelectMessage(utils.StringInt(vars["id"]))
if err != nil {
- http.Error(w, fmt.Sprintf("message #%v was not found", vars["id"]), http.StatusInternalServerError)
+ sendErrorJson(err, w, r)
return
}
err = message.Delete()
if err != nil {
- http.Error(w, fmt.Sprintf("message #%v could not be deleted %v", vars["id"], err), http.StatusInternalServerError)
+ sendErrorJson(err, w, r)
return
}
@@ -447,14 +344,14 @@ func apiMessageUpdateHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
message, err := core.SelectMessage(utils.StringInt(vars["id"]))
if err != nil {
- http.Error(w, fmt.Sprintf("message #%v was not found", vars["id"]), http.StatusInternalServerError)
+ sendErrorJson(fmt.Errorf("message #%v was not found", vars["id"]), w, r)
return
}
var messageBody *types.Message
decoder := json.NewDecoder(r.Body)
err = decoder.Decode(&messageBody)
if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
+ sendErrorJson(err, w, r)
return
}
@@ -462,7 +359,7 @@ func apiMessageUpdateHandler(w http.ResponseWriter, r *http.Request) {
message = core.ReturnMessage(messageBody)
_, err = message.Update()
if err != nil {
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+ sendErrorJson(err, w, r)
return
}
@@ -512,14 +409,23 @@ func apiServiceFailuresHandler(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(service.AllFailures())
}
+func sendErrorJson(err error, w http.ResponseWriter, r *http.Request) {
+ output := apiResponse{
+ Status: "error",
+ Error: err.Error(),
+ }
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(output)
+}
+
func sendUnauthorizedJson(w http.ResponseWriter, r *http.Request) {
- data := map[string]interface{}{
- "error": "unauthorized",
- "url": r.RequestURI,
+ output := apiResponse{
+ Status: "error",
+ Error: errors.New("not authorized").Error(),
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusUnauthorized)
- json.NewEncoder(w).Encode(data)
+ json.NewEncoder(w).Encode(output)
}
func isAPIAuthorized(r *http.Request) bool {
diff --git a/handlers/handlers_test.go b/handlers/handlers_test.go
index ccbdf654..dc66ca94 100644
--- a/handlers/handlers_test.go
+++ b/handlers/handlers_test.go
@@ -16,7 +16,6 @@
package handlers
import (
- "fmt"
"github.com/hunterlong/statup/core"
"github.com/hunterlong/statup/core/notifier"
_ "github.com/hunterlong/statup/notifiers"
@@ -175,53 +174,6 @@ func TestServicesHandler(t *testing.T) {
assert.True(t, isRouteAuthenticated(req))
}
-func TestCreateUserHandler(t *testing.T) {
- form := url.Values{}
- form.Add("username", "newuser")
- form.Add("password", "password123")
- form.Add("email", "info@okokk.com")
- form.Add("admin", "on")
- req, err := http.NewRequest("POST", "/users", strings.NewReader(form.Encode()))
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- assert.Nil(t, err)
- rr := httptest.NewRecorder()
- Router().ServeHTTP(rr, req)
- assert.Equal(t, 303, rr.Code)
- assert.True(t, isRouteAuthenticated(req))
-}
-
-func TestEditUserHandler(t *testing.T) {
- form := url.Values{}
- form.Add("username", "changedusername")
- form.Add("password", "##########")
- form.Add("email", "info@okokk.com")
- form.Add("admin", "on")
- req, err := http.NewRequest("POST", "/user/2", strings.NewReader(form.Encode()))
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- assert.Nil(t, err)
- rr := httptest.NewRecorder()
- Router().ServeHTTP(rr, req)
-
- req, err = http.NewRequest("GET", "/users", nil)
- assert.Nil(t, err)
- rr = httptest.NewRecorder()
- Router().ServeHTTP(rr, req)
- body := rr.Body.String()
- assert.Contains(t, body, "admin | ")
- assert.Contains(t, body, "changedusername | ")
- assert.Equal(t, 200, rr.Code)
- assert.True(t, isRouteAuthenticated(req))
-}
-
-func TestDeleteUserHandler(t *testing.T) {
- req, err := http.NewRequest("GET", "/user/2/delete", nil)
- assert.Nil(t, err)
- rr := httptest.NewRecorder()
- Router().ServeHTTP(rr, req)
- assert.Equal(t, 303, rr.Code)
- assert.True(t, isRouteAuthenticated(req))
-}
-
func TestUsersHandler(t *testing.T) {
req, err := http.NewRequest("GET", "/users", nil)
assert.Nil(t, err)
@@ -275,48 +227,6 @@ func TestHelpHandler(t *testing.T) {
assert.True(t, isRouteAuthenticated(req))
}
-func TestCreateHTTPServiceHandler(t *testing.T) {
- form := url.Values{}
- form.Add("name", "Crystal Castles - Kept")
- form.Add("domain", "https://www.youtube.com/watch?v=CfbCLwNlGwU")
- form.Add("method", "GET")
- form.Add("expected_status", "200")
- form.Add("interval", "30")
- form.Add("port", "")
- form.Add("timeout", "30")
- form.Add("check_type", "http")
- form.Add("post_data", "")
-
- req, err := http.NewRequest("POST", "/services", strings.NewReader(form.Encode()))
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- assert.Nil(t, err)
- rr := httptest.NewRecorder()
- Router().ServeHTTP(rr, req)
- assert.Equal(t, 303, rr.Code)
- assert.True(t, isRouteAuthenticated(req))
-}
-
-func TestCreateTCPerviceHandler(t *testing.T) {
- form := url.Values{}
- form.Add("name", "Local Postgres")
- form.Add("domain", "localhost")
- form.Add("method", "GET")
- form.Add("expected_status", "")
- form.Add("interval", "30")
- form.Add("port", "5432")
- form.Add("timeout", "30")
- form.Add("check_type", "tcp")
- form.Add("post_data", "")
-
- req, err := http.NewRequest("POST", "/services", strings.NewReader(form.Encode()))
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- assert.Nil(t, err)
- rr := httptest.NewRecorder()
- Router().ServeHTTP(rr, req)
- assert.Equal(t, 303, rr.Code)
- assert.True(t, isRouteAuthenticated(req))
-}
-
func TestServicesHandler2(t *testing.T) {
req, err := http.NewRequest("GET", "/services", nil)
assert.Nil(t, err)
@@ -325,36 +235,36 @@ func TestServicesHandler2(t *testing.T) {
body := rr.Body.String()
assert.Equal(t, 200, rr.Code)
assert.Contains(t, body, "Statup | Services")
- assert.Contains(t, body, "Crystal Castles - Kept")
- assert.Contains(t, body, "Local Postgres")
+ assert.Contains(t, body, "JSON Users Test")
+ assert.Contains(t, body, "JSON API Tester")
//assert.Contains(t, body, "️")
assert.True(t, isRouteAuthenticated(req))
}
func TestViewHTTPServicesHandler(t *testing.T) {
- req, err := http.NewRequest("GET", "/service/6", nil)
+ req, err := http.NewRequest("GET", "/service/5", nil)
assert.Nil(t, err)
rr := httptest.NewRecorder()
Router().ServeHTTP(rr, req)
body := rr.Body.String()
assert.Equal(t, 200, rr.Code)
- assert.Contains(t, body, "Crystal Castles - Kept Status")
+ assert.Contains(t, body, "Google DNS Status")
//assert.Contains(t, body, "️")
}
func TestViewTCPServicesHandler(t *testing.T) {
- req, err := http.NewRequest("GET", "/service/7", nil)
+ req, err := http.NewRequest("GET", "/service/5", nil)
assert.Nil(t, err)
rr := httptest.NewRecorder()
Router().ServeHTTP(rr, req)
body := rr.Body.String()
assert.Equal(t, 200, rr.Code)
- assert.Contains(t, body, "Local Postgres Status")
+ assert.Contains(t, body, "Google DNS Status")
//assert.Contains(t, body, "️")
}
func TestServicesDeleteFailuresHandler(t *testing.T) {
- req, err := http.NewRequest("GET", "/service/7/delete_failures", nil)
+ req, err := http.NewRequest("GET", "/service/5/delete_failures", nil)
assert.Nil(t, err)
rr := httptest.NewRecorder()
Router().ServeHTTP(rr, req)
@@ -363,43 +273,7 @@ func TestServicesDeleteFailuresHandler(t *testing.T) {
}
func TestFailingServicesDeleteFailuresHandler(t *testing.T) {
- req, err := http.NewRequest("GET", "/service/1/delete_failures", nil)
- assert.Nil(t, err)
- rr := httptest.NewRecorder()
- Router().ServeHTTP(rr, req)
- assert.Equal(t, 303, rr.Code)
- assert.True(t, isRouteAuthenticated(req))
-}
-
-func TestServicesUpdateHandler(t *testing.T) {
- form := url.Values{}
- form.Add("name", "The Bravery - An Honest Mistake")
- form.Add("domain", "https://www.youtube.com/watch?v=O8vzbezVru4")
- form.Add("method", "GET")
- form.Add("expected_status", "")
- form.Add("interval", "30")
- form.Add("port", "")
- form.Add("timeout", "15")
- form.Add("check_type", "http")
- form.Add("post_data", "")
- req, err := http.NewRequest("POST", "/service/6", strings.NewReader(form.Encode()))
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- assert.Nil(t, err)
- rr := httptest.NewRecorder()
- Router().ServeHTTP(rr, req)
-
- req, err = http.NewRequest("GET", "/service/6", nil)
- assert.Nil(t, err)
- rr = httptest.NewRecorder()
- Router().ServeHTTP(rr, req)
- body := rr.Body.String()
- assert.Equal(t, 200, rr.Code)
- assert.Contains(t, body, "The Bravery - An Honest Mistake Status")
- //assert.Contains(t, body, "️")
-}
-
-func TestDeleteServiceHandler(t *testing.T) {
- req, err := http.NewRequest("POST", "/service/7/delete", nil)
+ req, err := http.NewRequest("GET", "/service/5/delete_failures", nil)
assert.Nil(t, err)
rr := httptest.NewRecorder()
Router().ServeHTTP(rr, req)
@@ -486,7 +360,7 @@ func TestPrometheusHandler(t *testing.T) {
Router().ServeHTTP(rr, req)
body := rr.Body.String()
assert.Equal(t, 200, rr.Code)
- assert.Contains(t, body, "statup_total_services 6")
+ assert.Contains(t, body, "statup_total_services 5")
assert.True(t, isRouteAuthenticated(req))
}
@@ -622,42 +496,6 @@ func TestExportHandler(t *testing.T) {
assert.True(t, isRouteAuthenticated(req))
}
-func TestCreateBulkServices(t *testing.T) {
- domains := []string{
- "https://status.coinapp.io",
- "https://demo.statup.io",
- "https://golang.org",
- "https://github.com/hunterlong",
- "https://www.santamonica.com",
- "https://www.oeschs-die-dritten.ch/en/",
- "https://etherscan.io",
- "https://www.youtube.com/watch?v=ipvEIZMMILA",
- "https://www.youtube.com/watch?v=UdaYVxYF1Ok",
- "https://www.youtube.com/watch?v=yydZbVoCbn0&t=870s",
- "http://failingdomainsarenofunatall.com",
- }
- for k, d := range domains {
- form := url.Values{}
- form.Add("name", fmt.Sprintf("Test Service %v", k))
- form.Add("domain", d)
- form.Add("method", "GET")
- form.Add("expected_status", "200")
- form.Add("interval", fmt.Sprintf("%v", k+1))
- form.Add("port", "")
- form.Add("timeout", "30")
- form.Add("check_type", "http")
- form.Add("post_data", "")
-
- req, err := http.NewRequest("POST", "/services", strings.NewReader(form.Encode()))
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- assert.Nil(t, err)
- rr := httptest.NewRecorder()
- Router().ServeHTTP(rr, req)
- assert.Equal(t, 303, rr.Code)
- assert.True(t, isRouteAuthenticated(req))
- }
-}
-
func isRouteAuthenticated(req *http.Request) bool {
os.Setenv("GO_ENV", "production")
rr := httptest.NewRecorder()
diff --git a/handlers/route_api_test.go b/handlers/route_api_test.go
index ab73cd4d..6aeea75c 100644
--- a/handlers/route_api_test.go
+++ b/handlers/route_api_test.go
@@ -107,8 +107,8 @@ func TestApiAllServicesHandlerHandler(t *testing.T) {
var obj []types.Service
formatJSON(body, &obj)
assert.Equal(t, 200, rr.Code)
- assert.Equal(t, "Test Service 9", obj[0].Name)
- assert.Equal(t, "https://www.youtube.com/watch?v=yydZbVoCbn0&t=870s", obj[0].Domain)
+ assert.Equal(t, "Google", obj[0].Name)
+ assert.Equal(t, "https://google.com", obj[0].Domain)
}
func TestApiServiceHandler(t *testing.T) {
@@ -212,7 +212,6 @@ func TestApiViewUserHandler(t *testing.T) {
func TestApiUpdateUserHandler(t *testing.T) {
data := `{
"username": "adminupdated",
- "email": "info@email.com",
"password": "password123",
"admin": true}`
rr, err := httpRequestAPI(t, "POST", "/api/users/1", strings.NewReader(data))
@@ -220,6 +219,7 @@ func TestApiUpdateUserHandler(t *testing.T) {
body := rr.Body.String()
var obj types.User
formatJSON(body, &obj)
+ t.Log(body)
assert.Equal(t, 200, rr.Code)
assert.Equal(t, "adminupdated", obj.Username)
assert.Equal(t, true, obj.Admin.Bool)
diff --git a/handlers/routes.go b/handlers/routes.go
index 5492857b..3996c6fa 100644
--- a/handlers/routes.go
+++ b/handlers/routes.go
@@ -65,17 +65,10 @@ func Router() *mux.Router {
// USER Routes
r.Handle("/users", http.HandlerFunc(usersHandler)).Methods("GET")
- r.Handle("/users", http.HandlerFunc(createUserHandler)).Methods("POST")
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")
// MESSAGES Routes
r.Handle("/messages", http.HandlerFunc(messagesHandler)).Methods("GET")
- r.Handle("/messages", http.HandlerFunc(createMessageHandler)).Methods("POST")
- r.Handle("/message/{id}", http.HandlerFunc(viewMessageHandler)).Methods("GET")
- r.Handle("/message/{id}", http.HandlerFunc(updateMessageHandler)).Methods("POST")
- r.Handle("/message/{id}/delete", http.HandlerFunc(deleteMessageHandler)).Methods("GET")
// SETTINGS Routes
r.Handle("/settings", http.HandlerFunc(settingsHandler)).Methods("GET")
@@ -89,14 +82,13 @@ func Router() *mux.Router {
// SERVICE Routes
r.Handle("/services", http.HandlerFunc(servicesHandler)).Methods("GET")
- r.Handle("/services", http.HandlerFunc(createServiceHandler)).Methods("POST")
r.Handle("/services/reorder", http.HandlerFunc(reorderServiceHandler)).Methods("POST")
r.Handle("/service/{id}", http.HandlerFunc(servicesViewHandler)).Methods("GET")
- r.Handle("/service/{id}", http.HandlerFunc(servicesUpdateHandler)).Methods("POST")
r.Handle("/service/{id}/edit", http.HandlerFunc(servicesViewHandler))
- r.Handle("/service/{id}/delete", http.HandlerFunc(servicesDeleteHandler))
r.Handle("/service/{id}/delete_failures", http.HandlerFunc(servicesDeleteFailuresHandler)).Methods("GET")
r.Handle("/service/{id}/checkin", http.HandlerFunc(checkinCreateHandler)).Methods("POST")
+
+ // CHECKIN Routes
r.Handle("/checkin/{id}/delete", http.HandlerFunc(checkinDeleteHandler)).Methods("GET")
r.Handle("/checkin/{id}", http.HandlerFunc(checkinHitHandler))
diff --git a/handlers/users.go b/handlers/users.go
index 3eeb0690..c1b4d9e8 100644
--- a/handlers/users.go
+++ b/handlers/users.go
@@ -16,7 +16,8 @@
package handlers
import (
- "fmt"
+ "encoding/json"
+ "errors"
"github.com/gorilla/mux"
"github.com/hunterlong/statup/core"
"github.com/hunterlong/statup/types"
@@ -45,82 +46,116 @@ func usersEditHandler(w http.ResponseWriter, r *http.Request) {
executeResponse(w, r, "user.html", user, nil)
}
-func updateUserHandler(w http.ResponseWriter, r *http.Request) {
- if !IsAuthenticated(r) {
- http.Redirect(w, r, "/", http.StatusSeeOther)
+func apiUserHandler(w http.ResponseWriter, r *http.Request) {
+ if !isAPIAuthorized(r) {
+ sendUnauthorizedJson(w, r)
return
}
- r.ParseForm()
vars := mux.Vars(r)
- id := utils.StringInt(vars["id"])
- user, err := core.SelectUser(id)
+ user, err := core.SelectUser(utils.StringInt(vars["id"]))
if err != nil {
- utils.Log(3, fmt.Sprintf("user error: %v", err))
- w.WriteHeader(http.StatusInternalServerError)
+ sendErrorJson(err, w, r)
return
}
+ user.Password = ""
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(user)
+}
- user.Username = r.PostForm.Get("username")
- user.Email = r.PostForm.Get("email")
- isAdmin := r.PostForm.Get("admin") == "on"
- user.Admin = types.NewNullBool(isAdmin)
- password := r.PostForm.Get("password")
- if password != "##########" {
- user.Password = utils.HashPassword(password)
+func apiUserUpdateHandler(w http.ResponseWriter, r *http.Request) {
+ if !isAPIAuthorized(r) {
+ sendUnauthorizedJson(w, r)
+ return
}
+ vars := mux.Vars(r)
+ user, err := core.SelectUser(utils.StringInt(vars["id"]))
+ if err != nil {
+ sendErrorJson(err, w, r)
+ return
+ }
+ var updateUser *types.User
+ decoder := json.NewDecoder(r.Body)
+ decoder.Decode(&updateUser)
+ updateUser.Id = user.Id
+ user = core.ReturnUser(updateUser)
err = user.Update()
if err != nil {
- utils.Log(3, err)
- }
-
- fmt.Println(user.Id)
- fmt.Println(user.Password)
- fmt.Println(user.Admin.Bool)
-
- users, _ := core.SelectAllUsers()
- executeResponse(w, r, "users.html", users, "/users")
-}
-
-func createUserHandler(w http.ResponseWriter, r *http.Request) {
- if !IsAuthenticated(r) {
- http.Redirect(w, r, "/", http.StatusSeeOther)
+ sendErrorJson(err, w, r)
return
}
- r.ParseForm()
- username := r.PostForm.Get("username")
- password := r.PostForm.Get("password")
- email := r.PostForm.Get("email")
- admin := r.PostForm.Get("admin")
-
- user := core.ReturnUser(&types.User{
- Username: username,
- Password: password,
- Email: email,
- Admin: types.NewNullBool(admin == "on"),
- })
- _, err := user.Create()
- if err != nil {
- utils.Log(3, err)
- }
- //notifiers.OnNewUser(user)
- executeResponse(w, r, "users.html", user, "/users")
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(user)
}
-func usersDeleteHandler(w http.ResponseWriter, r *http.Request) {
- if !IsAuthenticated(r) {
- http.Redirect(w, r, "/", http.StatusSeeOther)
+func apiUserDeleteHandler(w http.ResponseWriter, r *http.Request) {
+ if !isAPIAuthorized(r) {
+ sendUnauthorizedJson(w, r)
return
}
vars := mux.Vars(r)
- id, _ := strconv.Atoi(vars["id"])
- user, _ := core.SelectUser(int64(id))
-
- users, _ := core.SelectAllUsers()
- if len(users) == 1 {
- utils.Log(2, "cannot delete the only user in the system")
- http.Redirect(w, r, "/users", http.StatusSeeOther)
+ users := core.CountUsers()
+ if users == 1 {
+ sendErrorJson(errors.New("cannot delete the last user"), w, r)
return
}
- user.Delete()
- http.Redirect(w, r, "/users", http.StatusSeeOther)
+ user, err := core.SelectUser(utils.StringInt(vars["id"]))
+ if err != nil {
+ sendErrorJson(err, w, r)
+ return
+ }
+ err = user.Delete()
+ if err != nil {
+ sendErrorJson(err, w, r)
+ return
+ }
+ output := apiResponse{
+ Object: "user",
+ Method: "delete",
+ Id: user.Id,
+ Status: "success",
+ }
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(output)
+}
+
+func apiAllUsersHandler(w http.ResponseWriter, r *http.Request) {
+ if !isAPIAuthorized(r) {
+ sendUnauthorizedJson(w, r)
+ return
+ }
+ users, err := core.SelectAllUsers()
+ if err != nil {
+ sendErrorJson(err, w, r)
+ return
+ }
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(users)
+}
+
+func apiCreateUsersHandler(w http.ResponseWriter, r *http.Request) {
+ if !isAPIAuthorized(r) {
+ sendUnauthorizedJson(w, r)
+ return
+ }
+ var user *types.User
+ decoder := json.NewDecoder(r.Body)
+ err := decoder.Decode(&user)
+ if err != nil {
+ sendErrorJson(err, w, r)
+ return
+ }
+ newUser := core.ReturnUser(user)
+ uId, err := newUser.Create()
+ if err != nil {
+ sendErrorJson(err, w, r)
+ return
+ }
+ output := apiResponse{
+ Object: "user",
+ Method: "create",
+ Id: uId,
+ Status: "success",
+ }
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(output)
}
diff --git a/notifiers/webhook_test.go b/notifiers/webhook_test.go
index a4b3d1d5..7bf7558c 100644
--- a/notifiers/webhook_test.go
+++ b/notifiers/webhook_test.go
@@ -23,7 +23,7 @@ import (
)
var (
- webhookTestUrl = "http://localhost:5555"
+ webhookTestUrl = "https://demo.statup.com/api/services"
webhookMessage = `{"id": "%service.Id","name": "%service.Name","online": "%service.Online","issue": "%failure.Issue"}`
apiKey = "application/json"
fullMsg string
@@ -99,7 +99,7 @@ func TestWebhookNotifier(t *testing.T) {
t.Run("webhooker Send", func(t *testing.T) {
err := webhook.Send(fullMsg)
- assert.Nil(t, err)
+ assert.Error(t, err)
assert.Equal(t, len(webhook.Queue), 1)
})
diff --git a/source/css/base.css b/source/css/base.css
index 5bdd6b4b..45a4d063 100644
--- a/source/css/base.css
+++ b/source/css/base.css
@@ -405,6 +405,9 @@ HTML, BODY {
.pointer {
cursor: pointer; }
+.jumbotron {
+ background-color: white; }
+
@media (max-width: 767px) {
HTML, BODY {
background-color: #fcfcfc; }
diff --git a/source/js/main.js b/source/js/main.js
index dd65246f..c6b771c7 100644
--- a/source/js/main.js
+++ b/source/js/main.js
@@ -59,11 +59,23 @@ $('.test_notifier').on('click', function(e) {
});
$('form').submit(function() {
- var spinner = '';
- $(this).find('button[type=submit]').prop('disabled', true);
- $(this).find('button[type=submit]').html(spinner);
+ // Spinner($(this).find('button[type=submit]'))
});
+
+function Spinner(btn, off = false) {
+ btn.prop('disabled', !off);
+ if (off) {
+ let pastVal = btn.attr("data-past");
+ btn.text(pastVal);
+ btn.removeAttr("data-past");
+ } else {
+ let pastVal = btn.text();
+ btn.attr("data-past", pastVal);
+ btn.html('');
+ }
+}
+
$('select#service_type').on('change', function() {
var selected = $('#service_type option:selected').val();
var typeLabel = $('#service_type_label');
@@ -122,6 +134,120 @@ function PingAjaxChart(chart, service, start=0, end=9999999999, group="hour") {
});
}
+$('.ajax_delete').on('click', function() {
+ let obj = $(this);
+ let id = obj.attr('data-id');
+ let element = obj.attr('data-obj');
+ let url = obj.attr('href');
+ let method = obj.attr('data-method');
+ $.ajax({
+ url: url,
+ type: method,
+ data: JSON.stringify({id: id}),
+ success: function (data) {
+ if (data.status === 'error') {
+ alert(data.error)
+ } else {
+ console.log(data);
+ $('#' + element).remove();
+ }
+ }
+ });
+ return false
+});
+
+
+$('form.ajax_form').on('submit', function() {
+ const form = $(this);
+ let values = form.serializeArray();
+ let method = form.attr('method');
+ let action = form.attr('action');
+ let func = form.attr('data-func');
+ let redirect = form.attr('data-redirect');
+ let button = form.find('button[type=submit]');
+ let alerter = form.find('#alerter');
+ var arrayData = [];
+ let newArr = {};
+ Spinner(button);
+ values.forEach(function(k, v) {
+ if (k.name === "password_confirm") {
+ return
+ }
+ if (k.value === "") {
+ return
+ }
+ if (k.value === "on") {
+ k.value = true
+ }
+ if($.isNumeric(k.value)){
+ if (k.name !== "password") {
+ k.value = parseInt(k.value)
+ }
+ }
+ newArr[k.name] = k.value;
+ arrayData.push(newArr)
+ });
+ let sendData = JSON.stringify(newArr);
+ console.log('sending '+method.toUpperCase()+' '+action+':', newArr);
+ $.ajax({
+ url: action,
+ type: method,
+ data: sendData,
+ success: function (data) {
+ console.log(data)
+ if (data.status === 'error') {
+ let alerter = form.find('#alerter');
+ alerter.html(data.error);
+ alerter.removeClass("d-none");
+ Spinner(button, true);
+ } else {
+ Spinner(button, true);
+ if (func) {
+ let fn = window[func];
+ if (typeof fn === "function") fn({form: newArr, data: data});
+ }
+ if (redirect) {
+ window.location.href = redirect;
+ }
+ }
+ }
+ });
+ return false;
+});
+
+function CreateService(output) {
+ console.log('creating service', output)
+ let form = output.form;
+ let data = output.data;
+ let objTbl = `
+ ${form.name} |
+ ${data.online}ONLINE |
+
+
+ |
+
`;
+ $('#services_table').append(objTbl);
+}
+
+function CreateUser(output) {
+ console.log('creating user', output)
+ let form = output.form;
+ let data = output.data;
+ let objTbl = `
+ ${form.username} |
+
+
+ |
+
`;
+ $('#users_table').append(objTbl);
+}
+
$('select#service_check_type').on('change', function() {
var selected = $('#service_check_type option:selected').val();
if (selected === 'POST') {
diff --git a/source/scss/base.scss b/source/scss/base.scss
index 9e5ffbd1..92f977c7 100644
--- a/source/scss/base.scss
+++ b/source/scss/base.scss
@@ -468,4 +468,8 @@ HTML,BODY {
cursor: pointer;
}
+.jumbotron {
+ background-color: white;
+}
+
@import 'mobile';
diff --git a/source/source.go b/source/source.go
index 092bcc60..82a8bbc1 100644
--- a/source/source.go
+++ b/source/source.go
@@ -199,7 +199,7 @@ func CopyAllToPublic(box *rice.Box, folder string) error {
if err != nil {
return nil
}
- filePath := fmt.Sprintf("%v%v", folder, path)
+ filePath := fmt.Sprintf("%v/%v", folder, path)
SaveAsset(file, utils.Directory, filePath)
return nil
})
diff --git a/source/tmpl/dashboard.html b/source/tmpl/dashboard.html
index c7a7e9bf..65a7c789 100644
--- a/source/tmpl/dashboard.html
+++ b/source/tmpl/dashboard.html
@@ -19,7 +19,14 @@
-
Services
+ {{if eq (len CoreApp.Services) 0}}
+
+ {{else}}
{{.SmallText}}
- {{ end }}
+ {{ end }}
+ {{end}}
{{ range Services }}
diff --git a/source/tmpl/form_message.html b/source/tmpl/form_message.html
index 4b637c89..6292eb95 100644
--- a/source/tmpl/form_message.html
+++ b/source/tmpl/form_message.html
@@ -1,6 +1,10 @@
{{define "form_message"}}
{{$message := .}}
-